Conversation
- updated pagination - fixed lint errors.
discover updates
discover updates
updated discovery
|
Important Review skippedReview was skipped as selected files did not have any reviewable changes. 💤 Files selected but had no reviewable changes (55)
You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughLarge, heterogeneous update adding/altering linting and tooling, many new or removed API routes (MeTokens, video-assets, livepeer, IPFS, swap), extensive documentation, Supabase/RLS and migration changes, player and upload UX updates, IPFS/gateway integrations, production-readiness fixes, and several client/server boundary and transaction-flow improvements. No subjective commentary. Changes
Sequence Diagram(s)sequenceDiagram
participant Frontend
participant API as Supabase Edge / Next API
participant Alchemy as Alchemy SDK
participant DB as Supabase Database
Frontend->>API: POST /api/metokens/alchemy {name,symbol,hubId,assetsDeposited,creatorAddress,...}
API->>DB: auth check / verify user (service role fallback)
API->>Alchemy: if txHash -> waitForMeTokenCreation(txHash)
Alchemy-->>API: tx confirmed -> meTokenAddress
API->>Alchemy: fetch on-chain MeToken info
Alchemy-->>API: protocol/token info
API->>DB: INSERT meTokens & metoken_transactions
DB-->>API: inserted record
API-->>Frontend: 201 { success, meToken, address, txHash }
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @sirgawain0x, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request delivers a substantial set of updates focused on improving user experience, enhancing core functionalities, and bolstering the application's stability and security. Key areas of improvement include a more robust video upload process, comprehensive error handling, seamless integration of smart accounts and MeTokens, and a new token swap feature. These changes aim to provide a more intuitive, reliable, and feature-rich platform for creators and users. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is a massive pull request that introduces a significant number of new features and improvements across the application, including a new swap widget with USD support, MeToken creation and trading enhancements, creator profiles, improved error handling, and fixes for memory leaks and UI bugs. The overall quality of the code is high, with robust logic, good error handling, and a focus on user experience. My review focuses on a couple of areas for improvement, but overall, this is an impressive set of updates.
| // The Livepeer SDK's getAll() method retrieves all assets | ||
| const response = await fullLivepeer.asset.getAll(); | ||
|
|
There was a problem hiding this comment.
This implementation fetches all assets from Livepeer on every request and then performs pagination on the server. This is highly inefficient and will not scale as the number of assets grows, potentially leading to high memory usage and slow API responses. The Livepeer SDK's getAll method might support server-side pagination parameters (e.g., limit, cursor). It's crucial to use these to only fetch the data needed for the current page. If the SDK does not support pagination, this approach will become a major performance bottleneck.
| // CORS headers for security | ||
| const corsHeaders = { | ||
| 'Access-Control-Allow-Origin': process.env.NODE_ENV === 'production' | ||
| ? 'https://yourdomain.com' // Replace with your actual domain |
There was a problem hiding this comment.
Hardcoding the production origin is not ideal. It's better to source this from an environment variable (e.g., process.env.PROD_URL) to make the application more configurable across different deployment environments (staging, production, etc.).
| ? 'https://yourdomain.com' // Replace with your actual domain | |
| ? 'https://your-production-domain.com' // TODO: Replace with process.env.PROD_URL |
There was a problem hiding this comment.
Actionable comments posted: 37
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
components/Navbar.tsx (1)
406-440: Fix TokenSelect desync after swapping tokensSwapping toggles
fromToken/toToken, butTokenSelectkeeps its ownselectedTokenstate (seecomponents/ui/token-select.tsx) and never reacts to prop changes. After the swap, the UI still shows the old token even though the state flipped, so users see the wrong asset before confirming. Either makeTokenSelectcontrolled (sync internal state whenvaluechanges) or force a remount here.- <TokenSelect + <TokenSelect + key={fromToken} value={fromToken} onChange={setFromToken} className="w-full" /> … - <TokenSelect + <TokenSelect + key={toToken} value={toToken} onChange={setToToken} className="w-full" />components/Videos/Upload/FileUpload.tsx (1)
198-207: Restore the uploaded URI update after a successful upload.
uploadedUrinever gets set anymore, so the post-upload success block (Lines 358-377) never renders and users lose the copy-to-clipboard IPFS URI. Wire the success path back topollForMetadataUri(or otherwise setuploadedUri) so we surface the link again:onSuccess() { console.log("Upload completed"); setUploadComplete(true); setUploadState("complete"); - if (uploadRequestResult?.asset?.id) - onFileUploaded(uploadRequestResult.asset.id); - else setError("Upload succeeded but asset ID is missing."); + if (uploadRequestResult?.asset?.id) { + onFileUploaded(uploadRequestResult.asset.id); + + void (async () => { + try { + const metadataUri = await pollForMetadataUri(uploadRequestResult.asset.id); + setUploadedUri(metadataUri); + } catch (pollErr) { + console.warn("Failed to resolve metadata URI:", pollErr); + } + })(); + } else { + setError("Upload succeeded but asset ID is missing."); + } },components/UserProfile/UserProfile.tsx (1)
99-180: Restore membership validation on UserProfile page
ProfilePageGuardnow only enforces login and omits membership gating—rewrap the page content inMembershipGuardor add equivalent membership checks (components/UserProfile/UserProfile.tsx).components/account-dropdown/AccountDropdown.tsx (1)
183-351: Hard-coded Base token addresses break send flow off Base.
TOKEN_INFOand the balance fetcher always use the Base deployment (USDC_TOKEN_ADDRESSES.base/DAI_TOKEN_ADDRESSES.base). When the user switches to Optimism (or any other supported chain) we still read balances and build transfers against the Base contracts, so reads revert and any send attempt will fail. Please key both the balance fetch and the ERC-20 transfer target offchain.id, falling back only when we truly don’t support that chain.components/UserProfile/MeTokensSection.tsx (2)
38-42: Fix stale state: sync nulls too (prevents ghost MeToken UI).Effect only sets local state when hookMeToken is truthy; it never clears on null, causing stale UI. Assign unconditionally.
Apply this diff:
- useEffect(() => { - if (hookMeToken) { - setUserMeToken(hookMeToken); - } - }, [hookMeToken]); + useEffect(() => { + setUserMeToken(hookMeToken || null); + }, [hookMeToken]);
293-297: Guard division by zero in price display.totalSupply can be 0; current code may render Infinity/NaN. Add a safe fallback.
Apply this diff:
- <p className="text-2xl font-bold"> - ${(userMeToken.tvl / (parseFloat(userMeToken.totalSupply.toString()) / 1e18)).toFixed(4)} - </p> + <p className="text-2xl font-bold"> + { + (parseFloat(userMeToken.totalSupply.toString()) > 0) + ? `$${(userMeToken.tvl / (parseFloat(userMeToken.totalSupply.toString()) / 1e18)).toFixed(4)}` + : '-' + } + </p>
🧹 Nitpick comments (43)
components/wallet/swap/DIRECT_CLIENT_FIX.md (1)
138-153: Fix markdownlint MD040 violations.Several fenced code blocks (e.g., Lines 138-153 and later “Traditional EIP-4337 Flow”) omit a language identifier, which triggers markdownlint MD040. Please annotate these blocks with an appropriate language (e.g.,
text,shell) so the lint step passes.app/vote/create/[address]/actions.ts (1)
19-19: Consider validating the address parameter against signer address.The
addressparameter is checked for presence but never validated againstsigner.instance.getAddress()or used in the proposal data. This could allow URL address and actual signer mismatch.Since this appears to be a pre-existing pattern (per AI summary noting no functional changes), you may optionally add validation:
// After line 51, before try block: const signerAddress = await signer.instance.getAddress(); if (address.toLowerCase() !== signerAddress.toLowerCase()) { return { success: false, error: "Address mismatch: URL address must match connected signer", } as ActionResponse; }Or remove the parameter if it's not needed and derive it from the signer instead.
Also applies to: 40-44
components/Player/DemoPlayer.tsx (1)
39-41: Restructure component to improve code clarity and enhance error message.While the guard works correctly (function declarations are hoisted), the current structure where helper functions are declared after the conditional return is confusing and violates typical React component organization. Additionally, the error message lacks styling and accessibility features.
Recommended improvements:
- Move function declarations before the guard for better code organization:
}, [currentPlayingId, playerId]); + const resetFadeTimeout = () => { + if (fadeTimeoutRef.current) { + clearTimeout(fadeTimeoutRef.current); + } + setControlsVisible(true); + fadeTimeoutRef.current = setTimeout(() => { + setControlsVisible(false); + }, 2000); + }; + + const handleControlInteraction = () => { + setControlsVisible(true); + resetFadeTimeout(); + }; + + const handlePlay = () => { + setCurrentPlayingId(playerId); + }; + if (!src || src.length === 0) { - return <div>No video source available.</div>; + return ( + <div className="flex h-full w-full items-center justify-center bg-black"> + <p className="text-lg text-white" role="alert"> + No video source available. + </p> + </div> + ); } - const resetFadeTimeout = () => { - if (fadeTimeoutRef.current) { - clearTimeout(fadeTimeoutRef.current); - } - setControlsVisible(true); - fadeTimeoutRef.current = setTimeout(() => { - setControlsVisible(false); - }, 2000); - }; - - const handleControlInteraction = () => { - setControlsVisible(true); - resetFadeTimeout(); - }; - - const handlePlay = () => { - setCurrentPlayingId(playerId); - }; - return (
- Enhanced error message includes:
- Styling to match the player's dark theme and dimensions
- Semantic
role="alert"for screen reader announcement- Centered layout consistent with the loading indicator pattern
components/UserProfile/CreatorProfileDisplay.tsx (1)
22-22: Consider adding type definitions for better type safety.The
meTokenobject returned from the hook is typed asany | null, which prevents compile-time detection of property access errors. Consider defining and using a proper TypeScript interface for the MeToken data shape to catch inconsistencies early.For example, create a type definition like:
interface MeTokenDisplay { name: string; symbol: string; tvl?: number; total_supply?: string | bigint; created_at?: string; }Then update the hook return type or cast the meToken appropriately. This would have caught the potential property name mismatch flagged in the previous comment.
components/SendTransaction.tsx (5)
182-186: Reuse viem’s erc20Abi; avoid ad-hoc parseAbiSimpler and less error‑prone.
- const transferCalldata = encodeFunctionData({ - abi: parseAbi(["function transfer(address,uint256) returns (bool)"]), - functionName: "transfer", - args: [recipient as Address, tokenAmount], - }); + const transferCalldata = encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [recipient as Address, tokenAmount], + });
130-131: Prefer form submit or drop preventDefaultonClick gets a MouseEvent; preventDefault isn’t needed without a
. Either wrap in a and set Button type="submit", or simplify handler signature.- const handleSend = async (e: React.FormEvent) => { - e.preventDefault(); + const handleSend = async () => {Also applies to: 357-361
88-89: Gate console logs or use a loggerAvoid noisy logs in production; add env guard or switch to structured logging.
Also applies to: 117-118, 188-195, 211-213
43-45: Avoid duplicating TokenSymbol; reuse shared typeReuse TokenSymbol from lib/sdk/alchemy/swap-service.ts (or centralize token config) to prevent drift.
231-233: Magic gas buffer (0.001 ETH)Consider estimating gas or using paymaster info to compute a dynamic buffer; 0.001 may be too high/low across networks.
app/upload/page.tsx (1)
19-26: Remove console logs before production deployment.The redirect logic correctly prioritizes smart account addresses, but the console logs should be removed or replaced with a proper logging solution for production.
Apply this diff to remove console logs:
useEffect(() => { - console.log('Upload redirect - EOA:', eoaAddress, 'SCA (client):', scaAddress, 'SCA (account):', account?.address); if (smartAccountAddress) { - console.log('Redirecting to Smart Account upload:', smartAccountAddress); router.replace(`/upload/${smartAccountAddress}`); } else if (eoaAddress) { - console.log('Redirecting to EOA upload:', eoaAddress); router.replace(`/upload/${eoaAddress}`); } }, [eoaAddress, smartAccountAddress, router, account?.address, scaAddress]);app/profile/page.tsx (1)
19-26: Remove console logs before production deployment.The redirect logic is correct, but console logs should be removed or replaced with a proper logging solution for production.
Apply this diff to remove console logs:
useEffect(() => { - console.log('Profile redirect - EOA:', eoaAddress, 'SCA (client):', scaAddress, 'SCA (account):', account?.address); if (smartAccountAddress) { - console.log('Redirecting to Smart Account profile:', smartAccountAddress); router.replace(`/profile/${smartAccountAddress}`); } else if (eoaAddress) { - console.log('Redirecting to EOA profile:', eoaAddress); router.replace(`/profile/${eoaAddress}`); } }, [eoaAddress, smartAccountAddress, router, account?.address, scaAddress]);components/wallet/swap/DEPLOY_ACCOUNT_GUIDE.md (2)
13-15: Minor: Add language specifier to fenced code block.The fenced code block at lines 13-15 should specify a language identifier for proper syntax highlighting and markdown linting compliance.
Apply this diff:
-``` +```text The error message shows: "Your smart account address: 0x..."--- `60-62`: **Minor: Add language specifier to fenced code block.** The fenced code block at lines 60-62 should specify a language identifier for markdown linting compliance. Apply this diff: ```diff -``` +```text https://basescan.org/address/YOUR_SMART_ACCOUNT_ADDRESS</blockquote></details> <details> <summary>components/IframeCleanup.tsx (1)</summary><blockquote> `44-65`: **Consider consolidating effects and removing beforeunload cleanup.** The two `useEffect` hooks could be consolidated, and the `beforeunload` listener may be unnecessary since: 1. Browsers automatically clean up DOM when navigating away 2. The pathname-triggered effect already handles route changes 3. Component unmount cleanup is sufficient for React navigation Apply this diff to consolidate: ```diff - useEffect(() => { - // Clean up iframes when route changes - cleanupExistingIframes(); - }, [pathname]); - useEffect(() => { - // Clean up on component mount + // Clean up on mount and route changes cleanupExistingIframes(); - - // Clean up before page unload - const handleBeforeUnload = () => { - cleanupExistingIframes(); - }; - - window.addEventListener('beforeunload', handleBeforeUnload); - return () => { - window.removeEventListener('beforeunload', handleBeforeUnload); - // Clean up on component unmount cleanupExistingIframes(); }; - }, []); + }, [pathname]);SUBTITLE_STORAGE_IMPLEMENTATION.md (1)
96-107: Add code block languages for lint compliance.Markdown lint is flagging the fenced blocks without language identifiers (e.g., the flow charts). Append an explicit language after each triple backtick to keep the docs passing mdlint.
components/ErrorBoundary.tsx (1)
29-35: Also clear the stored error when ignoring aborts.
setState({ hasError: false })leaves the previouserrorreference hanging around. Reset bothhasErroranderrorso abort cases fully clear the boundary state.- this.setState({ hasError: false }); + this.setState({ hasError: false, error: undefined });VERIFICATION_GUIDE.md (1)
27-36: Specify fenced code block languages to satisfy markdownlintMarkdownlint is flagging
MD040because the fenced blocks (e.g., Line 27) omit a language. Please add an explicit language liketextto these status-style snippets throughout the document to unblock the lint step.VIDEO_ASSET_FETCH_ERROR_FIX.md (1)
6-6: Add language identifier to fenced code block.The fenced code block should specify a language for proper syntax highlighting.
Apply this diff:
-``` +```text Failed to get video asset by playback ID: TypeError: fetch failed</blockquote></details> <details> <summary>ERROR_FIXES_SUMMARY.md (1)</summary><blockquote> `11-11`: **Add language identifiers to fenced code blocks.** Multiple fenced code blocks are missing language identifiers for proper syntax highlighting. Apply these diffs: ```diff -``` +```text GraphQL Error (Code: 500): Subgraph request failed Failed to fetch MeTokens from subgraph```diff -``` +```text CreateThumbnail: No asset ID provided!```diff -``` +```text ✅ Query key available 🔗 Forwarding to subgraph endpoint: https://subgraph.satsuma-prod.com/***/... ✅ Subgraph query successfulAlso applies to: 98-98, 114-114 </blockquote></details> <details> <summary>VIDEO_CODEC_VALIDATION.md (1)</summary><blockquote> `6-6`: **Add language identifier to fenced code block.** The fenced code block should specify a language for proper syntax highlighting. Apply this diff: ```diff -``` +```text Video transcoding failed: invalid video file codec or container, check your input file against the input codec and container support matrix</blockquote></details> <details> <summary>components/wallet/swap/ERROR_FIXES.md (1)</summary><blockquote> `39-90`: **Add languages to fenced code blocks** markdownlint (MD040) is flagging these fences. Please specify the language (e.g., ` ```typescript `, ` ```bash `, or ` ```text `) for each block so docs keep passing the lint checks. Based on static analysis hints ```diff -``` +```typescript const tokenAmount = await priceService.convertFromUSD(parseFloat(value), token);…and repeat for the remaining fences with the appropriate language.
SUBTITLE_PROCESSING_FIX_FINAL.md (1)
7-188: Label fenced code blocks with their languagesmarkdownlint (MD040) reports these fences because they lack language hints. Annotate them (
```bash,```typescript,```json, etc.) to keep the docs lint-clean.
Based on static analysis hints-``` +```bash curl -X POST "https://livepeer.studio/api/beta/generate/audio-to-text" \…and update the remaining blocks with the correct language.
components/Videos/Upload/Create-thumbnail.tsx (2)
101-103: Avoid repeated “Video is ready!” toasts on each pollThis fires every poll while phase === "ready". Gate it to fire once on transition.
Example:
const prevPhaseRef = useRef<string | undefined>(); useEffect(() => { const phase = livepeerAssetData?.status?.phase; if (phase === 'ready' && prevPhaseRef.current !== 'ready') { toast.success("Video is ready!", { duration: 2000 }); } prevPhaseRef.current = phase; }, [livepeerAssetData?.status?.phase]);
46-50: Reduce verbose console logs in productionConsole logs with asset internals every poll and error JSON dumps can be noisy. Consider gating behind a debug flag or using a proper logger at debug level.
Also applies to: 62-69, 74-98, 106-114
QUICK_FIX_GUIDE.md (1)
146-146: Update “Last Updated” timestampConsider updating the date to reflect this PR’s changes so readers trust the freshness of the guidance.
components/Videos/Upload/CreateThumbnailForm.tsx (6)
166-187: Handle non-2xx HTTP responses and JSON parsing errorsBoth fetch helpers assume response.json() yields a success shape. Check response.ok and guard JSON parsing to surface better errors.
Example:
const res = await fetch(url, { ... }); if (!res.ok) { const text = await res.text().catch(() => ''); throw new Error(text || `Request failed (${res.status})`); } const result = await res.json().catch(() => ({}));Also applies to: 189-223
31-39: assetReady prop is unusedEither use it for UI gating/disabled states or remove it to reduce surface area.
Also applies to: 41-46
538-552: Guard number parsing to avoid NaNNumber('') yields NaN. Consider coercing empty to 0 or undefined and validating accordingly.
Example:
onChange={(e) => { const v = e.target.value.trim(); field.onChange(v === '' ? 0 : Number(v)); }}
20-29: Remove unused selectedImage from FormValuesYou maintain selectedImage in component state; the form field isn’t used. Drop it to simplify types.
-interface FormValues { +interface FormValues { thumbnailType: "custom" | "ai"; customImage: File | null; aiPrompt: string; meTokenConfig: { requireMeToken: boolean; priceInMeToken: number; }; - selectedImage: string; }
68-73: Narrow aiImages typeUse a concrete shape to improve DX and prevent runtime surprises.
Example:
type AiImage = { id?: string; url: string }; const [aiImages, setAiImages] = useState<AiImage[]>([]);
175-176: Hard-coded price (1 USDC) should be configurableExtract to config or props for easier tuning and testing.
SUBTITLE_PROCESSING_ERROR_FIX.md (1)
7-10: Add a language hint to fenced code blocks.
markdownlint is flagging this block because it lacks a language identifier; please update the opening fence to something like ```text (or the appropriate language) to satisfy MD040 and keep our docs lint‑clean.app/api/video-assets/[id]/route.ts (1)
4-39: Consider extracting common route handler logic.This route follows the same pattern as the other video-asset routes (
by-asset-idandby-playback-id). The error handling, response formatting, and overall structure are duplicated across all three routes.Consider extracting a higher-order function or middleware to reduce duplication:
// lib/api/video-assets-handler.ts type VideoAssetFetcher<T> = (param: T) => Promise<any>; export function createVideoAssetHandler<T>( paramName: string, fetcher: VideoAssetFetcher<T>, validator?: (param: T) => boolean ) { return async ( request: NextRequest, { params }: { params: Promise<{ [key: string]: string }> } ) => { try { const resolvedParams = await params; const paramValue = resolvedParams[paramName] as T; if (validator && !validator(paramValue)) { return NextResponse.json( { error: `Invalid ${paramName}` }, { status: 400 } ); } const asset = await fetcher(paramValue); if (!asset) { return NextResponse.json( { error: "Video asset not found" }, { status: 404 } ); } return NextResponse.json(asset, { status: 200 }); } catch (error) { console.error(`[API] Error fetching video asset by ${paramName}:`, error); return NextResponse.json( { error: "Failed to fetch video asset", details: error instanceof Error ? error.message : "Unknown error", }, { status: 500 } ); } }; }Then use it in each route:
// app/api/video-assets/[id]/route.ts export const GET = createVideoAssetHandler( 'id', (id: string) => getVideoAssetById(parseInt(id, 10)), (id: string) => !isNaN(parseInt(id, 10)) );components/ui/pagination.tsx (1)
15-54: Add accessibility labels for screen readers.The pagination buttons lack
aria-labelattributes, which would help screen reader users understand the navigation controls better.Apply this diff to improve accessibility:
<Button onClick={onPrevPage} disabled={!hasPrevPage || isLoading} variant="outline" size="sm" className="flex items-center gap-2" + aria-label={`Go to previous page ${hasPrevPage ? `(page ${currentPage - 1})` : '(disabled)'}`} > <ChevronLeft className="h-4 w-4" /> Previous </Button> <div className="flex items-center gap-2 text-sm text-muted-foreground"> - <span>Page {currentPage}</span> + <span aria-current="page">Page {currentPage}</span> {totalDisplayed && <span>({totalDisplayed} videos)</span>} </div> <Button onClick={onNextPage} disabled={!hasNextPage || isLoading} variant="outline" size="sm" className="flex items-center gap-2" + aria-label={`Go to next page ${hasNextPage ? `(page ${currentPage + 1})` : '(disabled)'}`} > Next <ChevronRight className="h-4 w-4" /> </Button>app/api/livepeer/playback-info/route.ts (1)
4-34: LGTM! Consider adding response type validation.The route implementation is clean and follows good error handling practices. The use of query parameters is appropriate for this endpoint.
Consider validating the response structure from Livepeer to catch API changes early:
const result = await fullLivepeer.playback.get(playbackId); if (!result?.playbackInfo) { console.error('Invalid playback info response:', result); return NextResponse.json( { error: 'Invalid response from Livepeer' }, { status: 502 } ); } return NextResponse.json(result.playbackInfo);components/Videos/VideoThumbnail.tsx (2)
71-80: Improve loading state UX.When
isLoadingis true, the code immediately shows the Player component instead of displaying a loading skeleton or the thumbnail. This could cause unnecessary player initialization and a jarring UX.Apply this diff to show a loading state instead of the player:
// If we should show the player or no thumbnail is available, show the player - if (showPlayer || !thumbnailUrl || isLoading) { + if (showPlayer || (!thumbnailUrl && !isLoading)) { return ( <Player src={src} assetId={assetId} title={title} onPlay={onPlay} /> ); } + + // Show loading state + if (isLoading) { + return ( + <div className="relative aspect-video bg-gray-900 rounded-lg flex items-center justify-center"> + <div className="w-8 h-8 border-2 border-white border-t-transparent rounded-full animate-spin"></div> + </div> + ); + }
44-51: Remove debug console.log statements.Console.log statements should be removed or replaced with proper logging for production code.
Remove or replace the console.log statements:
if (dbAsset && 'thumbnail_url' in dbAsset && dbAsset.thumbnail_url) { - console.log('Found database thumbnail:', dbAsset.thumbnail_url); setThumbnailUrl(dbAsset.thumbnail_url); setIsLoading(false); return; } // If no database thumbnail, try Livepeer VTT thumbnails - console.log('No database thumbnail found, trying Livepeer VTT for:', playbackId); const url = await getThumbnailUrl(playbackId); setThumbnailUrl(url);components/UserProfile/MeTokensSection.tsx (2)
116-121: Use robust address validation (viem isAddress).String checks can pass invalid hex. Use viem’s isAddress for correctness.
Apply this diff:
- // Validate address format - if (!address.startsWith('0x') || address.length !== 42) { - alert('Invalid address format. Please enter a valid Ethereum address (0x followed by 40 hex characters).\n\n' + - 'Note: Do not paste the transaction hash - you need to find the MeToken contract address from the "Internal Txns" tab on Basescan.'); - return; - } + // Validate address format + if (!isAddress(address as `0x${string}`)) { + alert('Invalid address. Please enter a valid Ethereum address.\n\n' + + 'Note: Do not paste the transaction hash - you need the MeToken contract address from the "Internal Txns" tab on Basescan.'); + return; + }Also add the import at top:
-import { useState, useEffect } from 'react'; +import { useState, useEffect } from 'react'; +import { isAddress } from 'viem';
103-108: Replace blocking alerts with toasts for better UX.window.alert blocks UI. Prefer your existing toast/Alert patterns for consistency.
Example:
- Use useToast() and toast({ variant: 'destructive' | 'default', title, description }) for errors/info.
- For long guidance, render in-page as already done elsewhere.
Also applies to: 118-121, 142-152
components/wallet/funding/DaiFundingOptions.tsx (2)
35-37: Prefer chain-aware DAI address selection.Hardcoding Base limits multi-chain; derive from current chain.
Example approach:
- Import getDaiTokenContract and use the active chain (via useChain() or client.chain).
- Fallback to base if unknown.
-import { DAI_TOKEN_ADDRESSES } from '@/lib/contracts/DAIToken'; +import { getDaiTokenContract } from '@/lib/contracts/DAIToken'; ... - const daiContract = { - address: DAI_TOKEN_ADDRESSES.base as `0x${string}`, - abi: erc20Abi, - }; + const dai = getDaiTokenContract('base'); // TODO: choose from active chain
39-47: Guard missing account before balanceOf call.Avoid casting possibly undefined address.
Apply this diff:
- const balance = await client.readContract({ + const owner = client.account?.address as `0x${string}` | undefined; + if (!owner) throw new Error('Wallet not connected'); + const balance = await client.readContract({ address: daiContract.address, abi: daiContract.abi, functionName: 'balanceOf', - args: [client.account?.address as `0x${string}`], + args: [owner], }) as bigint;components/UserProfile/MeTokenTrading.tsx (2)
115-116: Prefer chain-aware DAI selection.Hardcoded 'base' reduces portability. Use chain from Account Kit or a config switch.
233-251: Optional: avoid flashing trading UI before subscription status resolves.If client isn’t ready, isSubscribed can be null and render trading. Consider showing the loader until known.
| 'Access-Control-Allow-Origin': process.env.NODE_ENV === 'production' | ||
| ? 'https://yourdomain.com' // Replace with your actual domain | ||
| : 'http://localhost:3000', // Development only | ||
| 'Access-Control-Allow-Methods': 'POST', | ||
| 'Access-Control-Allow-Headers': 'Content-Type, Authorization', | ||
| 'Access-Control-Max-Age': '86400', // 24 hours | ||
| }; |
There was a problem hiding this comment.
Fix the hard-coded CORS origin.
Access-Control-Allow-Origin is set to https://yourdomain.com, which will break production requests and conflicts with the existing middleware’s allowed-origin list. Wire this header to the real domains (or omit it and let the middleware handle CORS).
🤖 Prompt for AI Agents
In app/api/coinbase/session-token/route.ts around lines 7 to 13 the
Access-Control-Allow-Origin header is hard-coded to "https://yourdomain.com";
replace this by either removing the header here so the existing CORS middleware
handles origins, or read a real value (or list) from configuration (e.g.
process.env.CORS_ORIGIN or a comma-separated ALLOWED_ORIGINS) and set the header
conditionally for production only (or select the origin from the allowed list
based on request origin), ensuring the placeholder is not shipped to production.
There was a problem hiding this comment.
Actionable comments posted: 24
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
components/wallet/buy/wert-fund-button.tsx (1)
89-94: Loading state cleared prematurely.The
setLoading(false)in the finally block (line 93) executes immediately afterwidget.open()returns synchronously, but the widget loads asynchronously. This means:
- The button re-enables before the widget actually appears
- Users can click the button multiple times, potentially creating duplicate sessions
- The loading state doesn't accurately reflect the widget's loading state
Move
setLoading(false)into the widget listeners to reflect the actual widget state:listeners: { loaded: () => { // Close the dialog when the Wert widget is loaded onClose?.(); + setLoading(false); }, close: () => {}, "payment-status": (data: any) => {}, },And if you add an error listener (per previous review comment), also call
setLoading(false)there. Then removesetLoading(false)from the finally block, or conditionally set it only for errors that occur beforewidget.open():} catch (err) { setError("Failed to start Wert session. Please try again."); + setLoading(false); } finally { - setLoading(false); }app/vote/create/[address]/actions.ts (2)
20-20: Unused parameter: chainId is accepted but not used.The
chainIdparameter is defined in the schema (line 20) and destructured (line 37), but the chain is hardcoded tobaseon line 55. Either remove the unused parameter or use it to dynamically select the chain.Apply this diff to remove the unused parameter:
const createProposalSchema = z.object({ title: z.string().min(3, "Title is required"), content: z.string().min(3, "Content is required"), choices: z.array(z.string().min(1)).min(2, "At least two choices required"), start: z.number().int().positive(), end: z.number().int().positive(), address: z.string().min(1, "Wallet address required"), - chainId: z.number().int().positive(), });const { title, content, choices, start, end, address, chainId } = - parsedInput; + const { title, content, choices, start, end, address } = parsedInput;Also applies to: 37-37, 55-55
56-56: Restrict Alchemy API key to server-side only
The variable NEXT_PUBLIC_ALCHEMY_API_KEY is imported in client bundles (e.g. components/Navbar.tsx, TopChart.tsx) and will be exposed to users. If this key must remain private, rename it (e.g. ALCHEMY_API_KEY) and load it only in server-side code.components/Navbar.tsx (1)
406-425: TokenSelect stays on the old token after swappingWhen the swap button flips
fromToken/toToken, the TokenSelect UI still shows the previous choice because the component keeps its own state and never syncs when thevalueprop changes. Users see mismatched tokens. Add a prop-change effect (or make TokenSelect fully controlled) so the displayed token reflects the newvalue.export function TokenSelect({ value = "ETH", onChange, className = "" }: TokenSelectProps) { - const [isOpen, setIsOpen] = useState(false); - const [selectedToken, setSelectedToken] = useState( - TOKENS.find(t => t.symbol === value) || TOKENS[0] - ); + const [isOpen, setIsOpen] = useState(false); + const [selectedToken, setSelectedToken] = useState( + TOKENS.find(t => t.symbol === value) || TOKENS[0] + ); + + useEffect(() => { + const next = TOKENS.find((t) => t.symbol === value); + if (next) { + setSelectedToken(next); + } + }, [value]);
♻️ Duplicate comments (36)
components/wallet/swap/AA23_FIX_COMPLETE.md (2)
164-173: Add a language identifier to this code fence.The fenced block still lacks a language tag; add ```text (or similar) per markdownlint guidance.
-``` +```text 1. User enters swap amount
176-184: Add a language identifier to this code fence.Same here—tag the fence with a language for lint compliance.
-``` +```text 1. User enters swap amountcomponents/wallet/balance/TokenBalance.tsx (1)
100-118: Token read failures still masked as "0" balancesThe individual token catch blocks (lines 113-118 for USDC, lines 134-139 for DAI) still only log errors and set balances to
nullwithout callingsetError(). This causes failed reads to render as "0" in the UI (lines 235-237, 252-254), which is misleading to users who cannot distinguish between an actual zero balance and a read failure.The previous review comment on this issue has not been addressed.
Apply this diff to surface token read failures to users:
} catch (error) { if (isMounted && !signal.aborted) { console.error("Error fetching USDC balance:", error); - setUsdcBalance(null); + setError((prev) => prev ?? "Unable to load token balances"); } } … } catch (error) { if (isMounted && !signal.aborted) { console.error("Error fetching DAI balance:", error); - setDaiBalance(null); + setError((prev) => prev ?? "Unable to load token balances"); } }Also applies to: 120-139
components/wallet/buy/wert-fund-button.tsx (1)
78-82: Error handling and close listener remain unimplemented.The issues raised in the previous review are still present:
- No
errorlistener to handle widget load failures. Without this, if the Wert widget fails to load, the parent dialog remains open with no user feedback or recovery path.- The
closelistener is empty. Consider callingonClose?.()here to provide a consistent navigation experience when the user closes the Wert widget.Refer to the previous review comment for the recommended implementation pattern.
components/Player/HeroPlayer.tsx (1)
39-61: Move the empty-src guard below helper declarationsReturning before
resetFadeTimeoutis initialized resurrects the same ReferenceError noted earlier: the effect above still runs and callsresetFadeTimeout(), so whensrcis empty we throw “Cannot access 'resetFadeTimeout' before initialization.” Hoist the helper declarations (or declare them as functions) ahead of the guard, then keep the fallback check afterwards.- if (!src || src.length === 0) { - return <div>No video source available.</div>; - } - - const resetFadeTimeout = () => { + const resetFadeTimeout = () => { if (fadeTimeoutRef.current) { clearTimeout(fadeTimeoutRef.current); } setControlsVisible(true); fadeTimeoutRef.current = setTimeout(() => { setControlsVisible(false); }, 2000); }; const handleControlInteraction = () => { setControlsVisible(true); resetFadeTimeout(); }; const handlePlay = () => { setCurrentPlayingId(playerId); }; + + if (!src || src.length === 0) { + return <div>No video source available.</div>; + }components/Videos/Upload/CreateThumbnailForm.tsx (1)
88-125: Object URL memory leak still present.The previous review identified this exact issue:
URL.revokeObjectURLis called inside promise callbacks and returned fromthen/catch, so it never runs as effect cleanup, causing blob URL leaks.The pattern at lines 103-107 and 114-118:
.then(() => { const url = URL.createObjectURL(customImage); return () => URL.revokeObjectURL(url); // Never executes })Returning a function from inside a promise handler does not make it an effect cleanup. The returned function is simply ignored.
Apply the solution from the previous review or refactor to:
- Create the blob URL synchronously at the start of the effect
- Set it immediately to state for preview
- Return a cleanup function from the effect (not from promise handlers) that revokes the URL
- When IPFS upload succeeds, update state with the IPFS URL and explicitly revoke the blob URL
components/wallet/swap/AlchemySwapWidget.tsx (2)
181-193: Still blocking gasless swaps when paymaster is active.The previous review identified this issue: the component blocks accounts with
ethBalance === 0neven whenquote.feePayment?.sponsoredis true, preventing paymaster-backed swaps from proceeding.The balance check at lines 181-193 should only enforce the zero-balance error when swaps are NOT paymaster-sponsored:
- if (ethBalance === 0n) { + if (ethBalance === 0n && !swapState.quote?.feePayment?.sponsored) { console.warn('Account has zero ETH balance. This may cause swap failures.'); setSwapState(prev => ({ ...prev, error: 'Account has zero ETH balance. You need ETH for gas fees to perform swaps. Please add some ETH to your account first.' })); - } else { + } else if (ethBalance > 0n || swapState.quote?.feePayment?.sponsored) { // Clear any previous balance-related errors setSwapState(prev => ({ ...prev, error: prev.error?.includes('zero ETH balance') ? null : prev.error })); }Additionally, add
swapState.quote?.feePayment?.sponsoredto the effect's dependency array so it re-runs when sponsorship status changes.Also applies to lines 458-464 where similar logic exists.
520-565:isApprovingTokenflag never reset on success path.The previous review identified this issue:
setIsApprovingToken(true)at line 525 is never reset tofalseon the happy path, leaving the button stuck in "Approving..." state even after the swap succeeds.Apply this diff to reset the flag after approval completes:
console.log('✅ Approval confirmed! Hash:', approvalTxHash); console.log('✅ Proceeding to swap...'); + setIsApprovingToken(false); } else {Also reset the flag in the final success path around line 615-620:
setSwapState(prev => ({ ...prev, transactionHash: txHash, isSwapping: false, })); + setIsApprovingToken(false); onSwapSuccess?.();UPLOAD_PAGE_ERROR_FIX.md (1)
5-9: Add language identifier to code block.The code block is missing a language identifier for proper syntax highlighting and markdown compliance.
Apply this diff:
-``` +```console ❌ CreateThumbnail: No asset ID provided! This usually means the video upload did not complete successfully. Please check the FileUpload component logs.components/Videos/VideoThumbnail.tsx (1)
42-48: Replace unsafe type assertion with proper typing.The code uses
(dbAsset as any).thumbnail_urlwhich bypasses TypeScript's type safety and could lead to runtime errors.Apply this diff to fix the type assertion:
// First, try to get thumbnail from database const dbAsset = await fetchVideoAssetByPlaybackId(playbackId); - if (dbAsset && (dbAsset as any).thumbnail_url) { - console.log('Found database thumbnail:', (dbAsset as any).thumbnail_url); - setThumbnailUrl((dbAsset as any).thumbnail_url); + if (dbAsset && 'thumbnail_url' in dbAsset && dbAsset.thumbnail_url) { + console.log('Found database thumbnail:', dbAsset.thumbnail_url); + setThumbnailUrl(dbAsset.thumbnail_url); setIsLoading(false); return; }Better yet, define a proper type for the video asset response and update
fetchVideoAssetByPlaybackIdto return it.app/api/livepeer/actions.ts (1)
18-29: Critical: Client-side pagination will not scale.This implementation fetches ALL assets from Livepeer on every request and then performs pagination in memory. This is highly inefficient and will cause significant performance degradation and memory issues as the asset count grows.
The Livepeer SDK's asset methods likely support server-side pagination parameters (e.g.,
limit,cursor, oroffset). You must use these to fetch only the data needed for the current page.Run this script to check if the Livepeer SDK supports pagination parameters:
#!/bin/bash # Check Livepeer SDK pagination support rg -nP --type=ts -A5 -B5 'asset\.getAll|asset\.list' node_modules/livepeerIf the SDK doesn't support server-side pagination natively, consider:
- Using Livepeer's API directly with pagination parameters
- Implementing a caching layer to avoid repeated full fetches
- Warning users about performance limitations at scale
Based on learnings
app/api/coinbase/session-token/route.ts (2)
7-13: Fix hard-coded production CORS origin.
Access-Control-Allow-Originis set to the placeholderhttps://yourdomain.com, so real production requests will fail CORS. Make this dynamic (e.g., derive from env / request origin or drop this header and rely on the global middleware’s CORS handling).
102-192: Stop logging JWTs and API secrets.
console.loglines expose the freshly minted CDP JWT, API key JSON, and session-token payloads. These are sensitive credentials and must never hit logs. Please remove or mask these statements before shipping.app/api/creator-profiles/fix-address/route.ts (2)
2-2: Instantiate a server-side Supabase client for this routeThis still pulls in the browser helper, so the route will crash on the server and you only have anon-key privileges. Create a service-role client here instead.
-import { supabase } from '@/lib/sdk/supabase/client'; +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; +const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!; + +const supabase = createClient(supabaseUrl, supabaseServiceRoleKey, { + auth: { persistSession: false }, +});
109-114: Stop returning stack traces and raw error objects to clientsThe response still includes
details/errorType/fullError, leaking internals. Log server-side, but send a generic message.- return NextResponse.json( - { - error: 'Failed to fix profile address', - details: error instanceof Error ? error.message : 'Unknown error', - errorType: error instanceof Error ? error.constructor.name : typeof error, - fullError: JSON.stringify(error, Object.getOwnPropertyNames(error)) - }, - { status: 500 } - ); + return NextResponse.json( + { error: 'Failed to fix profile address' }, + { status: 500 } + );components/wallet/balance/DaiBalanceChecker.tsx (1)
13-63: Expose this logic as a hook and guard for missing account addressRendering
<DaiBalanceChecker />still throws because the function returns a plain object, and we also cast an undefined account address into the contract call. Convert this to a hook-style export and bail early when the smart account hasn’t been resolved.-interface DaiBalanceCheckerProps { - onBalanceUpdate?: (balance: bigint) => void; - className?: string; -} - -export function DaiBalanceChecker({ onBalanceUpdate, className }: DaiBalanceCheckerProps) { +interface UseDaiBalanceCheckerOptions { + onBalanceUpdate?: (balance: bigint) => void; +} + +export function useDaiBalanceChecker({ onBalanceUpdate }: UseDaiBalanceCheckerOptions = {}) { @@ - try { + try { + const accountAddress = client.account?.address; + + if (!accountAddress) { + setError('Smart account not connected'); + setIsLoading(false); + return; + } + const daiContract = { address: DAI_TOKEN_ADDRESSES.base as `0x${string}`, abi: erc20Abi, }; const balance = await client.readContract({ address: daiContract.address, abi: daiContract.abi, functionName: 'balanceOf', - args: [client.account?.address as `0x${string}`], + args: [accountAddress], }) as bigint; @@ - return { + return { @@ -} - -export default DaiBalanceChecker; +} + +export default useDaiBalanceChecker;components/ui/token-select.tsx (1)
3-33: Keep internal selection in sync with thevaluepropWhen the parent updates
value, the dropdown keeps showing the old token. Add an effect to realign internal state.-import { useState } from "react"; +import { useEffect, useState } from "react"; @@ const [selectedToken, setSelectedToken] = useState( TOKENS.find(t => t.symbol === value) || TOKENS[0] ); + + useEffect(() => { + const next = TOKENS.find((t) => t.symbol === value) || TOKENS[0]; + setSelectedToken(next); + }, [value]);app/api/swap/execute/route.ts (1)
91-96: Do not expose stack traces in the JSON response
detailsstill returns the stack to clients. Keep the logging, but respond with a generic message.- return NextResponse.json( - { - error: error instanceof Error ? error.message : 'Swap execution failed', - success: false, - details: error instanceof Error ? error.stack : undefined - }, - { status: 500 } - ); + return NextResponse.json( + { + error: error instanceof Error ? error.message : 'Swap execution failed', + success: false, + }, + { status: 500 } + );app/api/swap/debug/route.ts (1)
3-27: Remove the unauthenticated secret-dump endpoint.This GET route still leaks sensitive environment data (including private-key prefixes) to anyone who can hit it, with no auth or prod safeguards. That is a critical secret-exposure risk exactly like the already-flagged issue. Please delete the endpoint or, at minimum, gate it to dev-only and stop returning/ logging any part of secrets.
export async function GET(request: NextRequest) { - try { - const envCheck = { - hasApiKey: !!process.env.NEXT_PUBLIC_ALCHEMY_API_KEY, - hasPolicyId: !!process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID, - hasPrivateKey: !!process.env.ALCHEMY_SWAP_PRIVATE_KEY, - apiKeyPrefix: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY - ? process.env.NEXT_PUBLIC_ALCHEMY_API_KEY.slice(0, 10) + '...' - : 'MISSING', - policyIdPrefix: process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID - ? process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID.slice(0, 10) + '...' - : 'MISSING', - privateKeyPrefix: process.env.ALCHEMY_SWAP_PRIVATE_KEY - ? process.env.ALCHEMY_SWAP_PRIVATE_KEY.slice(0, 10) + '...' - : 'MISSING', - }; - - console.log('Environment debug check:', envCheck); - - return NextResponse.json({ - success: true, - environment: envCheck, - message: 'Environment variables check completed' - }); - ... + if (process.env.NODE_ENV !== 'development') { + return NextResponse.json({ error: 'Not available' }, { status: 403 }); + } + + const envCheck = { + hasApiKey: Boolean(process.env.NEXT_PUBLIC_ALCHEMY_API_KEY), + hasPolicyId: Boolean(process.env.NEXT_PUBLIC_ALCHEMY_PAYMASTER_POLICY_ID), + hasPrivateKey: Boolean(process.env.ALCHEMY_SWAP_PRIVATE_KEY), + }; + + return NextResponse.json({ + success: true, + environment: envCheck, + message: 'Environment variables check completed', + });components/SendTransaction.tsx (2)
39-176: Validate recipient and stop relying onparseFloatbalancesWe still cast
recipientstraight toAddressand compare balances withparseFloat. Please addisAddressfrom viem, reject invalid inputs before sending, and convert amounts/balances withparseUnits/formatUnitsso we stay BigInt-safe across 18‑decimals. Also derive token contract addresses from the connected chain instead of hardcoding.base.
278-291: Explorer link should respect the active chain
View on BaseScanstill assumes Base. Useclient.chain?.blockExplorers?.default?.url(with a fallback) so the button opens the right explorer for whatever chain the wallet is on.app/api/metokens/alchemy/route.ts (1)
144-153: Propagate insert errors when recording the creation transactionIf this insert hits RLS/network issues we still return 201, silently dropping the record. Capture
{ error }and fail the request when it’s non-null so the client knows to retry or alert.- await supabase - .from('metoken_transactions') - .insert({ + const { error: txError } = await supabase + .from('metoken_transactions') + .insert({ metoken_id: createdMeToken.id, user_address: creatorAddress.toLowerCase(), transaction_type: 'create', amount: parseFloat(assetsDeposited), transaction_hash: transactionHash, created_at: new Date().toISOString(), }); + + if (txError) { + console.error('Error recording MeToken transaction:', txError); + return NextResponse.json( + { error: 'Failed to record creation transaction' }, + { status: 500 } + ); + }components/wallet/balance/MeTokenBalances.tsx (1)
64-118: Clear the initial loading flag once hooks settle
isLoadingstaystrueforever, so anyone without holdings/userMeToken only ever sees the skeleton. TieisLoadingtomeTokenLoading/holdingsLoading(and errors) so we render the empty state once data loads.const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string | null>(null); const [showPortfolio, setShowPortfolio] = useState(false); // Get the user's MeToken data const { userMeToken, loading: meTokenLoading, error: meTokenError } = useMeTokensSupabase(); // Get all MeToken holdings const { holdings, loading: holdingsLoading } = useMeTokenHoldings(); + +useEffect(() => { + if (!meTokenLoading && !holdingsLoading) { + setIsLoading(false); + } +}, [meTokenLoading, holdingsLoading]);SUBTITLE_PROCESSING_FIX.md (1)
29-57: Align SUBTITLE_PROCESSING_FIX.md with code or implement missing subtitles props/handlers.The documentation lists new props and callbacks (
subtitlesUri,onSubtitlesProcessed,translateSubtitles,getLivepeerAudioToText) inCreate-thumbnail.tsx/CreateThumbnailForm.tsxthat aren't present in the code. Either update the doc to match the current implementation or add the described props and handlers.components/UserProfile/AlchemyMeTokenCreator.tsx (2)
338-338: Prevent render-time crashes when the amount field is invalid.
parseEther(assetsDeposited || '0')runs on every render; if the user types an intermediate non-numeric value (e.g., "1." or "e"),parseEtherthrows and the component crashes. Sanitize the input (or wrap the call in a safe parser) before invokingparseEtherso the UI stays stable while the user edits.Apply this diff to fix the issue:
- const hasEnoughDai = daiBalance >= parseEther(assetsDeposited || '0'); + const safeParseAmount = () => { + try { + return parseEther(assetsDeposited || '0'); + } catch { + return BigInt(0); + } + }; + const hasEnoughDai = daiBalance >= safeParseAmount();
214-240: Capture the actual MeToken address before calling callbacks.
extractMeTokenAddressalways returnsnull, so we persist an empty address to Supabase and invokeonMeTokenCreated('' , txHash). That breaks downstream gating because nothing knows which MeToken was just created. Please parse the transaction receipt (e.g.,client.waitForUserOperationReceipt→decodeEventLogfor the Subscribe/MeTokenCreated event) and feed the real address through before persisting or notifying listeners.components/Videos/Upload/index.tsx (1)
184-191: Guard against missing database asset ID before publishing.
videoAsset?.id as numberstill evaluates toundefinedwhenvideoAssethasn't been hydrated (e.g., DB insert failure or a stale render), so we end up callingupdateVideoAsset(undefined, …)and Supabase rejects the update. Bail out early if the ID is absent instead of force-casting.Apply this diff to enforce the guard:
- await updateVideoAsset(videoAsset?.id as number, { + if (!videoAsset?.id) { + toast.error("Missing video asset record. Please re-upload the video."); + return; + } + + await updateVideoAsset(videoAsset.id, {SUPABASE_RLS_SOLUTION.md (1)
22-40: Option 2 has the same security risks as Option 3.The RLS policies in Option 2 use
WITH CHECK (true)andUSING (true), which allow unrestricted public access to insert, update, and delete operations. This effectively provides the same level of access as disabling RLS entirely (Option 3), making it equally unsuitable for production environments.Apply this diff to add a security warning:
-### Option 2: Update RLS Policies (If you have database access) +### Option 2: Update RLS Policies (Not Recommended for Production) If you can modify the database, update the RLS policies to be more permissive: + +**⚠️ Security Warning**: The policies below allow unrestricted public access and should only be used in development environments. This approach bypasses authentication entirely. ```sqlcomponents/Videos/VideoCardGrid.tsx (1)
22-205: Restore pagination visibility on page 1.
totalPublishedAssetsis set topublishedAssets.length, i.e. the current page size, so it never exceedsITEMS_PER_PAGE. On the first pageshouldShowPagination()staysfalse, hiding the controls even whenhasNextPageis true—users can’t reach page 2+. Base the visibility check onhasNextPage(and/or the real total) instead of the per-page length, and drop the redundant state.- const [totalPublishedAssets, setTotalPublishedAssets] = useState<number>(0); ... - setTotalPublishedAssets(publishedAssets.length); ... - return totalPublishedAssets > ITEMS_PER_PAGE || currentPage > 1; - }, [totalPublishedAssets, currentPage]); + return hasNextPage || currentPage > 1; + }, [hasNextPage, currentPage]);components/UserProfile/UserProfile.tsx (1)
133-133: Preserve profile-specific upload context.The
href="/upload"link now always routes to the authenticated user's upload page, dropping the viewed profile'sdisplayAddress. If users should upload to the profile they're viewing, change it tohref={"/upload/${displayAddress}"}. Otherwise, hide or disable the upload button whendisplayAddress !== walletAddress.ALCHEMY_METOKEN_IMPLEMENTATION.md (1)
102-104: Correct the documented creation flow.The doc says "Creates MeToken using Diamond contract's
subscribefunction," but the actual implementation (seelib/hooks/metokens/useMeTokensSupabase.ts, lines 430-535) invokesMETOKEN_FACTORY.createto deploy the MeToken and only later mints viaDIAMOND.mint. Update the documentation so the described sequence matches the shipped code path.app/api/creator-profiles/upsert/route.ts (1)
5-93: Privilege escalation: upsert allows anyone to overwrite profiles.Because the handler never authenticates the caller, any client can POST arbitrary
owner_address(and new profile data) and—whenSUPABASE_SERVICE_ROLE_KEYis configured—bypass RLS entirely. That lets an attacker overwrite someone else's profile. Require an authenticated session and enforce that the caller owns the profile (e.g., read the Supabase user fromcreateClientand compare, or verify a signed wallet message) before performing the upsert, instead of unconditionally using the service-role client.As per coding guidelines
Example fix:
export async function POST(request: NextRequest) { try { const body = await request.json(); const { owner_address, username, bio, avatar_url } = body; if (!owner_address) { return NextResponse.json( { success: false, error: 'owner_address is required' }, { status: 400 } ); } // Authenticate the caller const supabase = await createClient(); const { data: { user } } = await supabase.auth.getUser(); if (!user) { return NextResponse.json( { success: false, error: 'Authentication required' }, { status: 401 } ); } // Verify the caller owns the profile // Assuming user.id or user.user_metadata contains the wallet address const authenticatedAddress = user.user_metadata?.address?.toLowerCase(); const requestedAddress = owner_address.toLowerCase(); if (authenticatedAddress !== requestedAddress) { return NextResponse.json( { success: false, error: 'Cannot modify another user\'s profile' }, { status: 403 } ); } // Now perform the upsert with the authenticated user's context const result = await supabase .from('creator_profiles') .upsert({ owner_address: requestedAddress, username, bio, avatar_url, updated_at: new Date().toISOString(), }, { onConflict: 'owner_address' }) .select(); // ... rest of error handling } catch (error) { // ... error handling } }components/UserProfile/MeTokenTrading.tsx (1)
93-200: Allowance/approval uses Diamond as spender instead of the vaultThis repeats the prior issue: checking and approving DAI against the Diamond contract (
0xba55…) is the wrong spender. Trading flows require allowance to the hub vault returned byDiamond.getHubInfo(hubId), andbuyMeTokensalready enforces that. Keeping this code issues redundant approvals that cost the user gas while still leaving the vault allowance untouched. Please either drop the local allowance logic and rely onbuyMeTokens’s helper or update the check/approve paths to resolve the vault first and target that address.components/wallet/funding/DaiFundingOptions.tsx (1)
62-165: Convert requiredAmount from wei before deriving the fiat preset.
requiredAmountis treated as a wei string everywhere else (BigInt(...), formatEther(...)). Parsing that string directly withparseFloatyields values on the order of 1e18, so the on-ramp request asks users to buy absurd sums and effectively breaks funding. Convert to ether first, then round.- const hasEnoughDai = requiredAmount ? daiBalance >= BigInt(requiredAmount) : daiBalance > BigInt(0); - const hasAnyDai = daiBalance > BigInt(0); + const hasAnyDai = daiBalance > BigInt(0); + const requiredAmountWei = requiredAmount !== undefined ? BigInt(requiredAmount) : null; + const hasEnoughDai = requiredAmountWei !== null ? daiBalance >= requiredAmountWei : hasAnyDai; + const presetFiatAmount = + requiredAmountWei !== null + ? Math.ceil(Number(formatEther(requiredAmountWei))) + : 50; @@ - <p>Your DAI balance: <span className="font-medium text-foreground">{formatEther(daiBalance)} DAI</span></p> - {requiredAmount && ( - <p>Required: <span className="font-medium text-foreground">{formatEther(BigInt(requiredAmount))} DAI</span></p> + <p>Your DAI balance: <span className="font-medium text-foreground">{formatEther(daiBalance)} DAI</span></p> + {requiredAmountWei !== null && ( + <p>Required: <span className="font-medium text-foreground">{formatEther(requiredAmountWei)} DAI</span></p> @@ - ? `You have ${formatEther(daiBalance)} DAI, but need ${requiredAmount ? formatEther(BigInt(requiredAmount)) : 'more'} DAI.` + ? `You have ${formatEther(daiBalance)} DAI, but need ${requiredAmountWei !== null ? formatEther(requiredAmountWei) : 'more'} DAI.` @@ - <DaiFundButton - presetAmount={requiredAmount ? Math.ceil(parseFloat(requiredAmount)) : 50} + <DaiFundButton + presetAmount={presetFiatAmount} @@ - {requiredAmount && ( - <p><strong>Required:</strong> {formatEther(BigInt(requiredAmount))} DAI</p> + {requiredAmountWei !== null && ( + <p><strong>Required:</strong> {formatEther(requiredAmountWei)} DAI</p> )}components/UserProfile/MeTokenCreator.tsx (1)
95-123: Fix the stale MeToken guard aftercheckUserMeToken().
checkUserMeToken()updates state asynchronously, so immediately readinguserMeTokenstill sees the old value and the guard can miss an existing token—exactly the bug we hit before. Capture and branch on the value returned bycheckUserMeToken(or have the hook return it) instead of the stale state snapshot before proceeding to subgraph checks.components/Videos/Upload/Create-thumbnail.tsx (1)
165-171: Fix stale thumbnailUri passed to onComplete.
setSelectedThumbnailis async, soselectedThumbnailstill holds the old value when you invokeonComplete. The previous review already called this out—the handler still forwards the stale state.if (livepeerAssetData) { setSelectedThumbnail(thumbnailUri); onComplete({ - thumbnailUri: selectedThumbnail as string, + thumbnailUri, meTokenConfig, });
🧹 Nitpick comments (25)
components/Videos/Upload/Stepper-Indicator.tsx (2)
15-23: Consider adding accessibility attributes for screen readers.The stepper items lack ARIA attributes that would improve accessibility for users relying on assistive technologies.
Consider adding accessibility attributes:
<div className={clsx( "m-1 flex h-8 w-8 items-center justify-center rounded-full border-2 text-xs sm:m-[5px] sm:h-[40px] sm:w-[40px] sm:text-base", step < activeStep && "bg-secondary text-[#EC407A]", step === activeStep && "border-primary text-primary" )} + role="status" + aria-label={`Step ${step} of 3${step < activeStep ? ' (completed)' : step === activeStep ? ' (current)' : ''}`} + aria-current={step === activeStep ? 'step' : undefined} > {step >= activeStep ? step : <Check className="h-3 w-3 sm:h-5 sm:w-5" />} </div>
18-18: Consider using a theme color instead of hardcoded hex value.The hardcoded color
text-[#EC407A]is inconsistent with the theme-based colors (bg-primary,text-primary,border-primary) used elsewhere in the component. This could make theme management and color consistency harder to maintain.Consider defining this pink color in your Tailwind theme configuration and using a semantic utility class like
text-accentortext-completed:-step < activeStep && "bg-secondary text-[#EC407A]", +step < activeStep && "bg-secondary text-accent",Then add to your Tailwind config:
// tailwind.config.ts theme: { extend: { colors: { accent: '#EC407A', // or completed: '#EC407A', } } }components/wallet/buy/wert-fund-button.tsx (3)
99-99: Consider extracting common button styling.The
className="w-full mb-4"is hardcoded on the button. If this styling pattern is used elsewhere in wallet components, consider defining it as a Button variant or extracting it to a constant for consistency.
53-53: Remove debug logging for production.The debug
console.logshould be removed or gated behind a development-only condition to avoid cluttering production logs.Apply this diff:
- const partnerId = process.env.NEXT_PUBLIC_WERT_PARTNER_ID; - console.log("DEBUG: NEXT_PUBLIC_WERT_PARTNER_ID =", partnerId); + const partnerId = process.env.NEXT_PUBLIC_WERT_PARTNER_ID;
20-20: Improve type safety for widget reference.The
wertWidgetRefis typed asany. Check if@wert-io/widget-initializerexports a type forWertWidgetinstances and use it:-const wertWidgetRef = useRef<any>(null); +const wertWidgetRef = useRef<WertWidget | null>(null);This would provide better type checking and IDE support for widget methods and properties.
components/wallet/swap/ConversionExample.tsx (2)
116-127: Consider validating hex input format.The hex input field accepts any string without validation. Users could enter invalid hex values (e.g., missing
0xprefix, non-hex characters), which would cause the conversion in theuseEffectto fail silently (lines 33-41).Add basic validation:
<div className="space-y-2"> <Label>Hexadecimal (Wei)</Label> <Input value={hexInput} - onChange={(e) => setHexInput(e.target.value)} + onChange={(e) => { + const value = e.target.value; + // Only update if it's a valid hex format or empty (for clearing) + if (value === '' || /^0x[0-9a-fA-F]*$/.test(value)) { + setHexInput(value); + } + }} placeholder="0x..." className="font-mono text-sm" />
50-86: Consider debouncing the input handlers.Both
handleTokenAmountChangeandhandleUsdChangetrigger async operations on every keystroke. If a user types quickly, multiple concurrent requests could complete out of order, leading to stale state or UI flicker. Debouncing would improve UX and reduce unnecessary API calls.Example using a simple debounce hook or utility:
import { useMemo } from 'react'; import { debounce } from 'lodash'; // or implement your own export function ConversionExample() { // ... existing state ... const debouncedTokenChange = useMemo( () => debounce(async (value: string) => { const sanitized = CurrencyConverter.sanitizeAmount(value, 18); const numValue = parseFloat(sanitized); if (!isNaN(numValue) && numValue > 0) { try { const hex = AlchemySwapService.formatAmount(sanitized, token); setHexInput(hex); const usd = await priceService.convertToUSD(numValue, token); setUsdValue(usd); } catch (error) { console.error('Error converting token to hex:', error); } } }, 300), [token] ); const handleTokenAmountChange = (value: string) => { setTokenAmount(value); debouncedTokenChange(value); }; // Similar pattern for handleUsdChange }app/api/x402/pay-for-ai-thumbnail/route.ts (1)
7-17: Consider externalizing payment configuration.The X402_CONFIG is well-structured for USDC/Base integration. However, once you implement the client-side payment flow, consider moving this configuration to a shared config file (e.g.,
app/config/x402/payment-config.ts) so it can be reused across client components and any server-side validation endpoints.app/vote/create/[address]/actions.ts (1)
54-60: Consider using a simpler public client for read-only operations.Creating a full
ModularAccountClientjust to callgetBlockNumber()is inefficient. Since this is a read-only operation and the smart account client isn't used for signing or sending transactions, consider using viem'screatePublicClientinstead.Apply this diff to use a lighter-weight public client:
- try { - // 1. Get the modular account client - const modularAccountClient = await createModularAccountClient({ - chain: base, // or your chain object - apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY as string, - }); - - // 2. Get the current block number (using viem or account kit client) - const block = await modularAccountClient.getBlockNumber(); + try { + // 1. Create a public client for read operations + const publicClient = createPublicClient({ + chain: base, + transport: http(`https://base-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`), + }); + + // 2. Get the current block number + const block = await publicClient.getBlockNumber();Don't forget to import
createPublicClientandhttpfrom viem at the top of the file.VIDEO_ASSET_FETCH_ERROR_FIX.md (1)
75-78: Add language identifier to fenced code block.The fenced code block starting at line 75 is missing a language identifier, which reduces readability and prevents syntax highlighting.
Apply this diff to add the language identifier:
-``` +```text Client Component → Server Function (with cookies()) → Supabase ❌ FAILS: Client can't use server-only APIsBased on the static analysis hint. </blockquote></details> <details> <summary>app/api/video-assets/by-asset-id/[assetId]/route.ts (1)</summary><blockquote> `11-16`: **Optional: Defensive check may be unnecessary.** The `assetId` parameter is extracted from the route path `[assetId]`, so Next.js ensures it's always present. This validation is defensive but redundant since the route pattern guarantees the parameter exists. Consider removing this check or adding a comment explaining it's for extra safety: ```diff - if (!assetId) { - return NextResponse.json( - { error: "Asset ID is required" }, - { status: 400 } - ); - } + // Note: assetId is guaranteed by route pattern, but we check for safety + if (!assetId) { + return NextResponse.json( + { error: "Asset ID is required" }, + { status: 400 } + ); + }Or simply remove it:
const { assetId } = await params; - - if (!assetId) { - return NextResponse.json( - { error: "Asset ID is required" }, - { status: 400 } - ); - } const asset = await getVideoAssetByAssetId(assetId);app/profile/page.tsx (1)
18-27: LGTM! Redirect logic correctly prioritizes smart account.The redirect flow appropriately prioritizes the smart account address over EOA, aligning with the Account Abstraction approach. The console logging is helpful for debugging the address resolution flow.
Consider removing console.log statements in production or using a proper logger:
- console.log('Profile redirect - EOA:', eoaAddress, 'SCA (client):', scaAddress, 'SCA (account):', account?.address); if (smartAccountAddress) { - console.log('Redirecting to Smart Account profile:', smartAccountAddress); router.replace(`/profile/${smartAccountAddress}`); } else if (eoaAddress) { - console.log('Redirecting to EOA profile:', eoaAddress); router.replace(`/profile/${eoaAddress}`); }components/IframeCleanup.tsx (2)
14-42: Iframe cleanup is functional but could be more efficient.The cleanup function uses multiple DOM queries and
innerHTML = ""which works but could be optimized.Consider these improvements:
const cleanupExistingIframes = () => { if (typeof window === "undefined") return; // Clean up container const container = document.getElementById("alchemy-signer-iframe-container"); if (container) { - container.innerHTML = ""; + // More explicit cleanup of child nodes + while (container.firstChild) { + container.removeChild(container.firstChild); + } } - // Remove all existing Turnkey iframes (there might be multiple) - const existingIframes = document.querySelectorAll('[id*="turnkey-iframe"], iframe[id="turnkey-iframe"]'); - existingIframes.forEach(iframe => { - try { - iframe.remove(); - } catch (error) { - console.warn("Failed to remove existing iframe:", error); - } - }); - - // Also check for any iframes with Turnkey-related IDs - const turnkeyIframes = document.querySelectorAll('iframe[id*="turnkey"], iframe[src*="turnkey"]'); - turnkeyIframes.forEach(iframe => { + // Remove all Turnkey-related iframes in one query + const turnkeyIframes = document.querySelectorAll( + 'iframe[id*="turnkey"], iframe[src*="turnkey"]' + ); + turnkeyIframes.forEach(iframe => { try { iframe.remove(); } catch (error) { console.warn("Failed to remove Turnkey iframe:", error); } }); };This consolidates the iframe queries and uses a more explicit cleanup method for the container.
49-65: Consider if beforeunload listener is necessary.The component cleans up iframes on mount, pathname change, and beforeunload. For SPA navigation in Next.js, the pathname-based cleanup (lines 44-47) should be sufficient. The
beforeunloadevent handler is useful for page reloads/closes but may be redundant for client-side navigation.If the pathname-based cleanup is sufficient, you could simplify to:
useEffect(() => { // Clean up on component mount cleanupExistingIframes(); - - // Clean up before page unload - const handleBeforeUnload = () => { - cleanupExistingIframes(); - }; - - window.addEventListener('beforeunload', handleBeforeUnload); return () => { - window.removeEventListener('beforeunload', handleBeforeUnload); // Clean up on component unmount cleanupExistingIframes(); }; }, []);However, keeping the
beforeunloadhandler provides defense-in-depth for browser refresh scenarios.components/ui/pagination.tsx (1)
26-51: Consider adding aria attributes for better accessibility.The pagination buttons have good visual affordance with icons and text, and proper disabled states. However, adding ARIA attributes would improve screen reader experience.
<Button onClick={onPrevPage} disabled={!hasPrevPage || isLoading} + aria-label={isLoading ? "Loading previous page" : "Go to previous page"} + aria-disabled={!hasPrevPage || isLoading} variant="outline" size="sm" className="flex items-center gap-2" > <ChevronLeft className="h-4 w-4" /> Previous </Button> {/* ... */} <Button onClick={onNextPage} disabled={!hasNextPage || isLoading} + aria-label={isLoading ? "Loading next page" : "Go to next page"} + aria-disabled={!hasNextPage || isLoading} variant="outline" size="sm" className="flex items-center gap-2" > Next <ChevronRight className="h-4 w-4" /> </Button>This helps screen reader users understand the current state and available actions.
app/api/livepeer/playback-info/route.ts (1)
4-34: Consider adding input validation for playbackId.The implementation is correct and follows standard API route patterns. However, adding validation for the playbackId format (e.g., checking if it matches expected patterns) could prevent unnecessary calls to Livepeer's API.
Example validation:
if (!playbackId) { return NextResponse.json( { error: 'Playback ID is required' }, { status: 400 } ); } + + // Validate playbackId format if known pattern exists + if (!/^[a-zA-Z0-9_-]+$/.test(playbackId)) { + return NextResponse.json( + { error: 'Invalid playback ID format' }, + { status: 400 } + ); + }app/discover/page.tsx (1)
5-5: Remove unused import.
OrbisVideoCardGridis imported but only used in commented-out code (lines 94-97).Apply this diff:
import VideoCardGrid from "@/components/Videos/VideoCardGrid"; import LivestreamGrid from "@/components/Live/LivestreamGrid"; -import OrbisVideoCardGrid from "@/components/Live/LivestreamGrid";VERIFICATION_GUIDE.md (1)
147-183: Add fence languages for markdownlint compliance.The code sections under “Current Configuration” and “Before Fix/After Fix” are missing language identifiers, triggering MD040. Please add something like
```textso the markdown linter passes.app/api/metokens/route.ts (1)
42-59: Consider more robust error classification beyond string matching.The current approach checks
error.message.includes('Failed to fetch MeToken')to distinguish 404 from 500. This is fragile because it depends on exact error message wording from the service layer. For maintainability, consider havingmeTokenSupabaseServicemethods throw typed errors (e.g.,NotFoundError,DatabaseError) so you can useinstanceofchecks instead of string matching.components/Videos/VideoDetails.tsx (1)
64-67: Avoid type assertion by narrowing the type properly.The
as anyassertion at line 66 bypasses TypeScript's type safety. Use a type guard instead to safely narrow the type.Apply this diff:
const row = await fetchVideoAssetByPlaybackId(asset.playbackId); if (row?.status) { const validStatuses = ["draft", "published", "minted", "archived"] as const; - if (validStatuses.includes(row.status as any)) { + type ValidStatus = (typeof validStatuses)[number]; + const isValidStatus = (status: string): status is ValidStatus => + (validStatuses as readonly string[]).includes(status); + + if (isValidStatus(row.status)) { - setDbStatus(row.status as "draft" | "published" | "minted" | "archived"); + setDbStatus(row.status); } }components/wallet/buy/dai-fund-button.tsx (2)
86-89: Consider verifying transaction completion instead of using an arbitrary delay.The 2-second delay at line 87 is arbitrary and may not be sufficient for transaction processing. Consider polling the user's DAI balance or transaction status instead of relying on a fixed timeout.
Example approach:
// Instead of fixed delay setTimeout(() => { onSuccess?.(); }, 2000); // Poll balance or transaction status const pollCompletion = async () => { for (let i = 0; i < 10; i++) { await new Promise(resolve => setTimeout(resolve, 1000)); // Check balance or transaction status const balance = await checkDaiBalance(address); if (balance >= expectedAmount) { onSuccess?.(); break; } } }; pollCompletion();
45-54: Add timeout and abort controller for the fetch request.The fetch call at line 45 has no timeout or abort controller, so it could hang indefinitely if the server doesn't respond. This is especially important for user-facing operations.
Apply this diff:
+ const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout + const res = await fetch("/api/coinbase/session-token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address, assets: ["DAI"], signature, message }), + signal: controller.signal, }); + + clearTimeout(timeoutId);TRANSCODING_TROUBLESHOOTING.md (1)
16-20: Add language identifier to the fenced code block.The static analysis tool flagged that the fenced code block starting at line 16 should have a language specified for syntax highlighting.
Based on learnings
Apply this diff:
**Check**: -``` +```javascript // Console should show: "Start upload - using smart account address: 0x..." "Upload completed"ERROR_FIXES_SUMMARY.md (1)
13-69: Add language identifiers to fenced code blocks.Several code fences are missing a language hint, which trips markdownlint (MD040) and loses syntax highlighting. Please tag each block with the appropriate language (e.g.,
bash,env,typescript) for clarity and to satisfy the linter.Also applies to: 86-110
TRANSCODING_ERROR_FIX.md (1)
7-56: Label fenced code blocks with a language.Markdownlint (MD040) flags these unlabeled fences, and adding identifiers (e.g.,
text,typescript) improves readability. Please tag the code blocks throughout this doc.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
components/ui/select.tsx (1)
7-7: Remove unused import.The
optimismimport is no longer used sincesupportedChainsnow only containsbase(line 178).Apply this diff to remove the unused import:
-import { base, optimism } from "@account-kit/infra"; +import { base } from "@account-kit/infra";Note: If line 243 in
getChainIconneeds to remain for backward compatibility (displaying the icon when users' wallets are still on Optimism), replaceoptimism.idwith the numeric literal10.case base.id: return "/images/chains/base.svg"; - case optimism.id: + case 10: // Optimism chain ID return "/images/chains/optimism.svg";app/api/livepeer/token-gate/route.ts (2)
66-78: Tighten timestamp validation to reject future timestamps.The current implementation uses
Math.abs(now - payload.timestamp)which allows timestamps up to 5 minutes in the future. This could be exploited by attackers who pre-generate access keys with future timestamps, potentially bypassing time-based access controls.Apply this diff to reject future timestamps while allowing a small clock skew:
- // Validate timestamp age < 5 minutes - const MAX_TIMESTAMP_AGE = 5 * 60 * 1000; + // Validate timestamp: not too old (<5 min) and not in future (allow 30s clock skew) + const MAX_TIMESTAMP_AGE = 5 * 60 * 1000; + const MAX_CLOCK_SKEW = 30 * 1000; const now = Date.now(); - if (Math.abs(now - payload.timestamp) > MAX_TIMESTAMP_AGE) { + const age = now - payload.timestamp; + if (age > MAX_TIMESTAMP_AGE || age < -MAX_CLOCK_SKEW) { return NextResponse.json( { allowed: false, - message: "Request timestamp too old or from future", + message: age > MAX_TIMESTAMP_AGE + ? "Request timestamp too old" + : "Request timestamp is in the future", }, { status: 400 } );
268-274: Implement asset accessibility checks before production use.The
checkAssetAccessibilityfunction currently returnstruefor all assets, effectively bypassing this validation step. This is a security gap that allows access to potentially restricted or unpublished content.The implementation should verify:
- Asset publication status
- Content restrictions or private flags
- Creator-defined access rules
- Asset existence in the database
Would you like me to generate an implementation that queries the asset metadata from your database/storage layer? Please provide the data source (e.g., Supabase table structure, API endpoint) and I can create the validation logic.
components/Navbar.tsx (1)
177-198: Stop re-creating the viem client each render.
createPublicClientruns on every render and the effect depends onpublicClient, so any re-render (copy toast, menu toggle, theme switch, etc.) triggers a fresh ENS lookup. In practice this hammers Alchemy on every state change and will eventually rate-limit the UI. Hoist the client behinduseMemo(or a module-level singleton) and keep the effect keyed only on the selected address.-import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useMemo } from "react"; ... - const publicClient = createPublicClient({ - chain: mainnet, - transport: alchemy({ - apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY as string, - }), - }); + const publicClient = useMemo( + () => + createPublicClient({ + chain: mainnet, + transport: alchemy({ + apiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY as string, + }), + }), + [] + );
♻️ Duplicate comments (4)
components/wallet/balance/TokenBalance.tsx (2)
112-117: Don't mask USDC read failures with "0" balances.The USDC catch block logs the error and sets balance to null without updating the error state. This causes the UI to display "0" for USDC when the read actually failed, misinforming users. Surface the failure by calling
setErrorso the error card renders instead of a misleading zero balance.Apply this diff:
} catch (error) { if (isMounted && !signal.aborted) { console.error("Error fetching USDC balance:", error); - setUsdcBalance(null); + setError((prev) => prev ?? "Unable to load USDC balance"); } }
133-137: Don't mask DAI read failures with "0" balances.The DAI catch block logs the error and sets balance to null without updating the error state. This causes the UI to display "0" for DAI when the read actually failed. Surface the failure by calling
setErrorso the error card renders instead of a misleading zero balance.Apply this diff:
} catch (error) { if (isMounted && !signal.aborted) { console.error("Error fetching DAI balance:", error); - setDaiBalance(null); + setError((prev) => prev ?? "Unable to load DAI balance"); } }components/wallet/swap/AlchemySwapWidget.tsx (1)
467-486: Let sponsored swaps bypass the gas buffer checkWe already skip the 0.001 ETH guard when
feePayment?.sponsoredis true, but this block still forces ETH swappers to hold an extra 0.001 ETH even when the paymaster is covering gas. That defeats the gasless flow and will stop sponsored ETH→token swaps for users who only hold the swap amount. Gate this branch behind!swapState.quote?.feePayment?.sponsored(or adjust the total needed calculation so the gas buffer is omitted when sponsored).
Raise priority so paymaster-backed swaps actually work without extra ETH.- if (swapState.fromToken === 'ETH') { + if (swapState.fromToken === 'ETH' && !swapState.quote?.feePayment?.sponsored) { const quoteData = swapState.quote.data; const swapAmount = BigInt(quoteData.value || '0x0'); // Need swap amount + buffer for gas const totalNeeded = swapAmount + minGasEth;components/account-dropdown/AccountDropdown.tsx (1)
175-553: Unblock multi-chain ERC-20 support before shipping.
TOKEN_INFOhardcodes the Base addresses, and both the balance reads (Lines 331‑344) and the send path (Lines 533‑552) reuse those addresses. The moment the user switches to Base Sepolia (or any other supported chain), every readContract/select/send call points at the Base deployment and the user operation reverts. Please resolve the token address from the active chain id, bail out with a toast when we lack coverage, and reuse that derived address everywhere (balances + transfers). The prior review already called this out; it still needs to be fixed.-const TOKEN_INFO = { - USDC: { ... , address: USDC_TOKEN_ADDRESSES.base }, - DAI: { ... , address: DAI_TOKEN_ADDRESSES.base }, -} as const; +const TOKEN_INFO = { + USDC: { ..., addresses: USDC_TOKEN_ADDRESSES }, + DAI: { ..., addresses: DAI_TOKEN_ADDRESSES }, +} as const;Then derive:
const chainKey = chain?.id === base.id ? "base" : chain?.id === baseSepolia.id ? "baseSepolia" : undefined; const tokenAddress = chainKey ? TOKEN_INFO[token].addresses[chainKey] : undefined; if (!tokenAddress) { toast({ variant: "destructive", title: "Unsupported network", description: `No ${token} deployment for ${chain?.name}` }); return; }Use
tokenAddressfor the balance fetches and the ERC‑20 transfer target so the modal works on every supported network.
🧹 Nitpick comments (9)
components/wallet/balance/TokenBalance.tsx (3)
12-17: Remove unused interface.The
TokenBalanceDatainterface is defined but never used in the component. The component uses individual state variables (usdcBalance,daiBalance,isLoading,error) instead of this interface.Apply this diff to remove the unused interface:
-interface TokenBalanceData { - symbol: string; - balance: string; - isLoading: boolean; - error: string | null; -} - // Utility function to format balance with proper precision (without symbol)
99-138: Consider extracting token balance fetching logic.The USDC and DAI balance fetching logic is nearly identical, differing only in the contract getter, state setter, and error message. This duplication makes the code harder to maintain.
Consider extracting a reusable helper function:
async function fetchTokenBalance( tokenType: 'usdc' | 'dai', chainKey: keyof typeof import("@/lib/contracts/USDCToken").USDC_TOKEN_ADDRESSES, address: `0x${string}`, publicClient: any, signal: AbortSignal ): Promise<bigint | null> { try { if (signal.aborted) return null; const contract = tokenType === 'usdc' ? getUsdcTokenContract(chainKey) : getDaiTokenContract(chainKey); return (await publicClient.readContract({ address: contract.address, abi: contract.abi, functionName: "balanceOf", args: [address], })) as bigint; } catch (error) { console.error(`Error fetching ${tokenType.toUpperCase()} balance:`, error); throw error; } }Then update the main logic:
try { const usdcBalance = await fetchTokenBalance('usdc', chainKey, address as `0x${string}`, publicClient, signal); if (isMounted && !signal.aborted) setUsdcBalance(usdcBalance); } catch (error) { if (isMounted && !signal.aborted) { setError((prev) => prev ?? "Unable to load USDC balance"); } } try { const daiBalance = await fetchTokenBalance('dai', chainKey, address as `0x${string}`, publicClient, signal); if (isMounted && !signal.aborted) setDaiBalance(daiBalance); } catch (error) { if (isMounted && !signal.aborted) { setError((prev) => prev ?? "Unable to load DAI balance"); } }
170-195: Consider extracting token row component.The token display rows for USDC and DAI are duplicated across loading and success states with nearly identical structure. Extracting a reusable component would reduce duplication and improve maintainability.
Consider creating a TokenRow component:
interface TokenRowProps { symbol: string; logoPath: string; value?: string; isLoading?: boolean; } function TokenRow({ symbol, logoPath, value, isLoading }: TokenRowProps) { return ( <div className="flex items-center justify-between"> <div className="flex items-center space-x-2"> <Image src={logoPath} alt={symbol} width={16} height={16} className="w-4 h-4" /> <span className="text-sm">{symbol}</span> </div> {isLoading ? ( <Skeleton className="h-5 w-16" /> ) : ( <span className="text-sm font-medium">{value}</span> )} </div> ); }Then simplify the component usage:
<CardContent className="space-y-2"> <TokenRow symbol="USDC" logoPath="/images/tokens/usdc-logo.svg" value={usdcBalance ? formatBalance(formatUnits(usdcBalance, 6)) : "0"} isLoading={isLoading} /> <TokenRow symbol="DAI" logoPath="/images/tokens/dai-logo.svg" value={daiBalance ? formatBalance(formatUnits(daiBalance, 18)) : "0"} isLoading={isLoading} /> </CardContent>Also applies to: 222-255
components/ui/select.tsx (1)
239-248: Consider removing unreachable Optimism case or adding explanatory comment.The
optimism.idcase (lines 243-244) is no longer reachable through thesupportedChainsdropdown since Optimism was removed. However, this case might still serve a purpose if:
- Users' wallets could be on Optimism when they load the page (defensive coding)
- The chain state from
useChain()could be Optimism before switchingIf the Optimism case is intentionally kept for displaying users' current chain state, consider adding a comment explaining this. Otherwise, remove it to keep the code clean.
If the case should be removed:
function getChainIcon(chainId: number): string { switch (chainId) { case base.id: return "/images/chains/base.svg"; - case optimism.id: - return "/images/chains/optimism.svg"; default: return "/images/chains/default-chain.svg"; } }If the case should be kept for backward compatibility, add a comment:
function getChainIcon(chainId: number): string { switch (chainId) { case base.id: return "/images/chains/base.svg"; + // Keep Optimism case for users whose wallets may still be on Optimism case optimism.id: return "/images/chains/optimism.svg"; default: return "/images/chains/default-chain.svg"; } }components/UserProfile/MeTokensSection.tsx (2)
109-153: Improve address validation and use toast for user feedback.The enhanced manual check has good improvements but could be further refined:
Use toast instead of alert(): Lines 116-118, 140, 145, and 149 use
alert()which blocks the UI. Replace with toast notifications for better UX.Strengthen address validation: Line 115 validates the format but doesn't verify that the characters after
0xare valid hexadecimal. Consider using a regex pattern or viem'sisAddress()utility.Variable consistency: Line 135 calls
checkSpecificMeToken(manualMeTokenAddress.trim())but you've already stored the trimmed value in theaddressvariable (line 112). Useaddressfor consistency.Example validation using viem:
import { isAddress } from 'viem'; // Replace lines 114-119 with: if (!isAddress(address)) { // Show toast error instead of alert return; }For variable consistency:
- const result = await checkSpecificMeToken(manualMeTokenAddress.trim()); + const result = await checkSpecificMeToken(address);
305-307: Consider adding refresh logic toonProfileUpdatedcallback.The
CreatorProfileManagercomponent receives an emptyonProfileUpdatedcallback. If profile updates should trigger any UI refresh (e.g., updating displayed profile data elsewhere), consider adding appropriate logic here.Example:
-<CreatorProfileManager targetAddress={walletAddress} onProfileUpdated={() => {}} /> +<CreatorProfileManager + targetAddress={walletAddress} + onProfileUpdated={handleRefresh} +/>components/Videos/Upload/FileUpload.tsx (3)
79-98: Move constants outside the component.The
SUPPORTED_VIDEO_FORMATSandSUPPORTED_VIDEO_EXTENSIONSarrays are recreated on every render. Move them outside the component as top-level constants for better performance.Apply this diff:
+// Livepeer supported video formats +// Containers: MP4, MOV, MKV, WebM, FLV, TS +// Video codecs: H.264, H.265 (HEVC), VP8, VP9, AV1 +const SUPPORTED_VIDEO_FORMATS = [ + 'video/mp4', + 'video/quicktime', // .mov + 'video/x-matroska', // .mkv + 'video/webm', + 'video/x-flv', + 'video/mp2t', // .ts + 'video/mpeg', +]; + +const SUPPORTED_VIDEO_EXTENSIONS = [ + '.mp4', + '.mov', + '.mkv', + '.webm', + '.flv', + '.ts', + '.mpeg', + '.mpg', +]; + const FileUpload: React.FC<FileUploadProps> = ({ onFileSelect, onFileUploaded, onPressNext, onPressBack, metadata, newAssetTitle, }) => { // ... component code - // Livepeer supported video formats - // Containers: MP4, MOV, MKV, WebM, FLV, TS - // Video codecs: H.264, H.265 (HEVC), VP8, VP9, AV1 - const SUPPORTED_VIDEO_FORMATS = [ - 'video/mp4', - 'video/quicktime', // .mov - 'video/x-matroska', // .mkv - 'video/webm', - 'video/x-flv', - 'video/mp2t', // .ts - 'video/mpeg', - ]; - - const SUPPORTED_VIDEO_EXTENSIONS = [ - '.mp4', - '.mov', - '.mkv', - '.webm', - '.flv', - '.ts', - '.mpeg', - '.mpg', - ];
127-155: Consider removing production console logs.Line 154 logs the selected file name, address, and account type. While useful for debugging, this could expose sensitive information in production builds.
Consider wrapping with a development check:
setSelectedFile(file); onFileSelect(file); - console.log("Selected file:", file?.name, "Address:", address, "Type:", type); + if (process.env.NODE_ENV === 'development') { + console.log("Selected file:", file?.name, "Address:", address, "Type:", type); + } };
205-212: Improve metadata polling error handling.The async IIFE for metadata polling is fire-and-forget (using
void). If polling fails, the error is only logged to the console and the user won't see the IPFS URI. Consider showing a toast notification to inform the user that metadata is still processing or if it failed.void (async () => { try { const metadataUri = await pollForMetadataUri(uploadRequestResult.asset.id); setUploadedUri(metadataUri); + toast.success("IPFS metadata URI generated!"); } catch (pollErr) { console.warn("Failed to resolve metadata URI:", pollErr); + toast.warning("Upload succeeded but IPFS metadata is still processing. Check back later."); } })();
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
ALCHEMY_METOKEN_IMPLEMENTATION.md(1 hunks)SUPABASE_VIDEO_ARCHITECTURE.md(1 hunks)VIEW_SYNC_SETUP.md(1 hunks)app/api/coinbase/session-token/route.ts(0 hunks)app/api/creator-profiles/fix-address/route.ts(1 hunks)app/api/livepeer/token-gate/route.ts(2 hunks)app/api/video-assets/published/route.ts(1 hunks)app/api/video-assets/sync-views/[playbackId]/route.ts(1 hunks)app/api/video-assets/sync-views/cron/route.ts(1 hunks)app/discover/page.tsx(3 hunks)components/Navbar.tsx(16 hunks)components/UserProfile/MeTokensSection.tsx(7 hunks)components/Videos/Upload/FileUpload.tsx(11 hunks)components/Videos/VideoCard.tsx(7 hunks)components/Videos/VideoCardGrid.tsx(1 hunks)components/Videos/VideoSearch.tsx(1 hunks)components/account-dropdown/AccountDropdown.tsx(14 hunks)components/ui/select.tsx(1 hunks)components/ui/token-select.tsx(1 hunks)components/wallet/balance/TokenBalance.tsx(5 hunks)components/wallet/swap/AlchemySwapWidget.tsx(1 hunks)
💤 Files with no reviewable changes (1)
- app/api/coinbase/session-token/route.ts
✅ Files skipped from review due to trivial changes (1)
- ALCHEMY_METOKEN_IMPLEMENTATION.md
🚧 Files skipped from review as they are similar to previous changes (1)
- app/api/creator-profiles/fix-address/route.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/blockchain-infra.mdc)
**/*.{ts,tsx,js,jsx}: Use only Viem for blockchain interactions (avoid alternative libraries like ethers.js or web3.js)
Use only Account Kit for account-abstraction-related functionality
Leverage Account Abstraction SDKs: @aa-sdk/core, @account-kit/infra, and @account-kit/react for smart account integration, deployment, and usage
Choose the appropriate smart account type; prefer ModularAccountV2 unless project needs dictate LightAccount, MultiOwnerLightAccount, or MultiOwnerModularAccount; note switching types deploys a different account and may require upgrades
Handle bundler and RPC traffic separately by using split transport when calling createSmartAccountClient
Configure gas and fee estimation when creating SmartAccountClient; provide custom gasEstimator and feeEstimator as needed or use alchemyFeeEstimator where appropriate
Manage paymasters for gas sponsorship via paymasterAndData in SmartAccountClient configuration
Implement gas abstraction via Gas Manager API by creating a gas policy and linking its policyId in client configuration
Integrate third-party paymasters correctly; provide custom gasEstimator and paymasterAndData as required; use erc7677Middleware for ERC-7677 compliant paymasters
Utilize viem primitives: createClient for building clients, getContract for contract interactions, and actions for blockchain operations
Understand and correctly handle UserOperationRequest: send, wait for mining, handle failures, and resubmit using drop-and-replace when necessary
Files:
components/ui/select.tsxcomponents/Videos/VideoCard.tsxapp/api/livepeer/token-gate/route.tsapp/api/video-assets/published/route.tscomponents/UserProfile/MeTokensSection.tsxcomponents/Videos/Upload/FileUpload.tsxapp/api/video-assets/sync-views/[playbackId]/route.tsapp/discover/page.tsxcomponents/Videos/VideoCardGrid.tsxcomponents/account-dropdown/AccountDropdown.tsxcomponents/wallet/balance/TokenBalance.tsxcomponents/wallet/swap/AlchemySwapWidget.tsxapp/api/video-assets/sync-views/cron/route.tscomponents/Navbar.tsxcomponents/Videos/VideoSearch.tsxcomponents/ui/token-select.tsx
🧠 Learnings (3)
📚 Learning: 2024-12-04T05:02:55.124Z
Learnt from: liberalterian
PR: creativeplatform/crtv3#80
File: package.json:19-20
Timestamp: 2024-12-04T05:02:55.124Z
Learning: The use of `kreisler/js-google-translate-free` in `FileUpload.tsx` is a temporary measure and will be replaced with the new `/api/livepeer/translation/route.ts` endpoint.
Applied to files:
components/Videos/Upload/FileUpload.tsx
📚 Learning: 2025-09-20T04:39:46.689Z
Learnt from: CR
PR: creativeplatform/crtv3#0
File: .cursor/rules/blockchain-infra.mdc:0-0
Timestamp: 2025-09-20T04:39:46.689Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use only Account Kit for account-abstraction-related functionality
Applied to files:
components/Navbar.tsx
📚 Learning: 2025-09-20T04:39:46.689Z
Learnt from: CR
PR: creativeplatform/crtv3#0
File: .cursor/rules/blockchain-infra.mdc:0-0
Timestamp: 2025-09-20T04:39:46.689Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Leverage Account Abstraction SDKs: aa-sdk/core, account-kit/infra, and account-kit/react for smart account integration, deployment, and usage
Applied to files:
components/Navbar.tsx
🧬 Code graph analysis (14)
components/Videos/VideoCard.tsx (2)
lib/utils/video-assets-client.ts (1)
fetchVideoAssetByPlaybackId(17-49)app/api/livepeer/views.ts (1)
fetchAllViews(3-44)
app/api/video-assets/published/route.ts (1)
services/video-assets.ts (1)
getPublishedVideoAssets(213-260)
components/UserProfile/MeTokensSection.tsx (3)
lib/hooks/metokens/useMeTokensSupabase.ts (6)
useMeTokensSupabase(106-1054)useMeTokensSupabase(342-791)MeTokenInfo(314-324)MeTokenData(326-340)supabaseMeToken(357-398)meTokenAddress(736-753)lib/sdk/metokens/subgraph.ts (1)
meTokensSubgraph(363-363)components/UserProfile/CreatorProfileManager.tsx (1)
CreatorProfileManager(23-265)
components/Videos/Upload/FileUpload.tsx (2)
app/api/livepeer/assetUploadActions.ts (1)
getLivepeerUploadUrl(7-65)components/Videos/Upload/index.tsx (2)
data(157-323)livepeerAsset(119-150)
app/api/video-assets/sync-views/[playbackId]/route.ts (2)
app/api/livepeer/views.ts (1)
fetchAllViews(3-44)lib/sdk/supabase/server.ts (1)
createClient(4-28)
app/discover/page.tsx (2)
components/Videos/VideoSearch.tsx (1)
VideoSearch(50-161)components/account-dropdown/MembershipSection.tsx (2)
MembershipSection(38-141)MembershipDetails(111-111)
components/Videos/VideoCardGrid.tsx (3)
lib/types/video-asset.ts (1)
VideoAsset(4-36)lib/utils/published-videos-client.ts (1)
fetchPublishedVideos(25-57)components/ui/pagination.tsx (1)
Pagination(15-54)
components/account-dropdown/AccountDropdown.tsx (5)
lib/sdk/alchemy/swap-service.ts (2)
TokenSymbol(10-10)TOKEN_INFO(13-17)lib/contracts/USDCToken.ts (2)
USDC_TOKEN_DECIMALS(9-9)USDC_TOKEN_ADDRESSES(3-6)lib/contracts/DAIToken.ts (2)
DAI_TOKEN_DECIMALS(9-9)DAI_TOKEN_ADDRESSES(3-6)components/wallet/swap/AlchemySwapWidget.tsx (1)
AlchemySwapWidget(34-1007)components/wallet/balance/MeTokenBalances.tsx (1)
MeTokenBalances(60-258)
components/wallet/balance/TokenBalance.tsx (2)
lib/contracts/USDCToken.ts (1)
getUsdcTokenContract(11-18)lib/contracts/DAIToken.ts (1)
getDaiTokenContract(11-18)
components/wallet/swap/AlchemySwapWidget.tsx (2)
lib/sdk/alchemy/swap-service.ts (4)
TokenSymbol(10-10)BASE_TOKENS(4-8)AlchemySwapService(107-365)alchemySwapService(368-371)lib/sdk/alchemy/price-service.ts (2)
priceService(226-226)PriceService(14-223)
app/api/video-assets/sync-views/cron/route.ts (2)
lib/sdk/supabase/service.ts (1)
createServiceClient(14-32)app/api/livepeer/views.ts (1)
fetchAllViews(3-44)
components/Navbar.tsx (1)
components/ui/token-select.tsx (1)
TokenSelect(25-120)
components/Videos/VideoSearch.tsx (1)
lib/hooks/useDebounce.ts (1)
useDebounce(9-25)
SUPABASE_VIDEO_ARCHITECTURE.md (4)
app/discover/[id]/page.tsx (2)
VideoDetailsPage(56-108)id(24-54)components/Videos/Upload/index.tsx (3)
data(157-323)livepeerAsset(119-150)dbAsset(146-149)components/Videos/VideoDetails.tsx (3)
fetchPlaybackSources(55-85)asset(60-66)VideoDetails(47-506)services/video-assets.ts (1)
updateVideoAsset(137-170)
🪛 Gitleaks (8.28.0)
VIEW_SYNC_SETUP.md
[high] 122-123: Discovered a potential authorization token provided in a curl command header, which could compromise the curl accessed resource.
(curl-auth-header)
🪛 markdownlint-cli2 (0.18.1)
SUPABASE_VIDEO_ARCHITECTURE.md
13-13: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
27-27: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
69-69: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
317-317: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (13)
components/ui/select.tsx (1)
176-222: Confirm Base-only chain selector impact
- Only occurrences of Optimism are in components/ui/select.tsx: the import and icon mapping in getChainIcon; no other code references Optimism.
- Selector now restricts to Base; users on Optimism (or other chains) will see their chain’s icon/name but won’t be able to switch.
- Ensure any user-facing messaging or documentation is updated to reflect Base-only support.
app/api/livepeer/token-gate/route.ts (2)
185-227: LGTM!The access validation logic is well-structured with proper error handling. The function correctly:
- Validates the access key
- Checks token balances
- Verifies asset accessibility
- Returns false on any error (fail-closed security model)
5-5: Verify Base-only restriction for token-gate API
No chain ID 10 (Optimism) references found in the token-gate context; confirm no clients invoke this route on Optimism and update docs/UI to reflect Base-only support.components/UserProfile/MeTokensSection.tsx (4)
55-60: LGTM! Data refresh without page reload.The new
handleRefreshfunction correctly invokescheckUserMeTokento refresh MeToken data without requiring a full page reload, improving user experience.
216-237: LGTM! Clear explanation of address ownership.The new alert provides helpful context about the difference between Smart Account and Controller addresses, improving user understanding of MeToken ownership. The conditional rendering ensures it only shows when relevant.
293-297: LGTM! Safe price calculation.The ternary operator correctly handles the edge case where
totalSupplyis zero, preventing division by zero and displaying a meaningful fallback (-).
375-458: LGTM! Comprehensive user guidance.The enhanced UI sections provide excellent user guidance:
- Clear instructions for finding the MeToken address from Basescan
- Auto-sync option for convenience
- Helpful error recovery guidance for common "Already Owns MeToken" issues
These additions significantly improve the user experience and reduce potential confusion.
components/Videos/Upload/FileUpload.tsx (6)
73-74: Good use of Universal Account for smart account address.The integration correctly uses the smart account address (SCA) for upload operations rather than the controller wallet address. This aligns with Account Abstraction best practices.
163-166: Good wallet connection check before upload.Properly gates the upload action behind a connected wallet, providing clear user feedback.
248-263: Clear loading and connection states.The UI properly handles both the loading state while fetching the universal account and the unconnected state, providing clear user feedback in both cases.
292-302: Excellent user guidance for video formats.The added informative messages clearly explain supported formats, required codecs, and even provide a helpful link to HandBrake for conversion. This will significantly reduce user confusion and support requests.
324-337: Good touch and mobile optimizations.The buttons properly handle touch interactions with
WebkitTapHighlightColor: 'transparent'and include responsive sizing. The disabled states and visual feedback are well implemented.Also applies to: 400-421
28-35: Confirm removal of onSubtitlesUploaded No code references remain; all consumers are updated accordingly.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/Player/Subtitles.tsx (1)
98-137: Add null check before accessing subtitles.The component calls
Object.keys(subtitles as Subtitles)(line 122) without first checking ifsubtitlesis defined. Since the context type allowssubtitlesto beundefined, this will throw a runtime error when the component renders before subtitles are loaded.Note:
SubtitlesDisplay(line 159) correctly handles this with optional chaining:subtitles?.[language].Apply this diff to add a null check:
export function SubtitlesLanguageSelect() { const { subtitles, language, setLanguage } = useSubtitles(); + if (!subtitles) { + return null; + } + return ( <div className="flex flex-col gap-2"> <label className="text-xs font-medium text-white/90" htmlFor="languageSelect" > Subtitles Language </label> <Select onValueChange={(value) => setLanguage(value)}> <FormControl> <SelectTrigger className={`inline-flex h-7 items-center justify-between gap-1 rounded-sm bg-gray-400 px-1 text-xs leading-none outline-none outline-1 outline-transparent/50`} > <SelectValue className="bg-gray-400 px-1 text-xs leading-none outline-none hover:bg-gray-300 active:bg-gray-300" placeholder="English" /> </SelectTrigger> </FormControl> <SelectContent className="overflow-hidden rounded-sm bg-gray-400 outline-none"> - {Object.keys(subtitles as Subtitles).map((language, i) => { + {Object.keys(subtitles).map((language, i) => { return ( <SelectItem className="bg-gray-400 px-1 text-xs leading-none outline-none hover:bg-gray-300 active:bg-gray-300" value={language} key={i} > {language} </SelectItem> ); })} </SelectContent> </Select> </div> ); }
♻️ Duplicate comments (6)
components/home-page/NonLoggedInView.tsx (1)
8-12: Duplicate hook invocation (see parent component).This hook is also invoked in the parent
Homecomponent (line 8 ofapp/page.tsx). See the review comment on that file for details on the duplicate invocation issue and the recommended fix.components/Videos/Upload/index.tsx (1)
178-186: Guard against missingvideoAsset.idbefore publishing
videoAsset?.id as numbercollapses toundefinedwhen the state hasn’t hydrated yet (e.g., DB insert failed), soupdateVideoAsset(undefined, …)will throw. Bail out early when the ID is missing instead of force-casting.- await updateVideoAsset(videoAsset?.id as number, { + if (!videoAsset?.id) { + toast.error( + "We couldn’t find the saved video record. Please retry the upload." + ); + return; + } + + await updateVideoAsset(videoAsset.id, {components/Videos/Upload/FileUpload.tsx (2)
198-201: Don’t fall back tovideo/mp4metadataIf
selectedFile.typeis empty, we end up labeling every upload asvideo/mp4, which misidentifies valid non-MP4 files and defeats the stricter validation above. Rely on the validated MIME type instead of hardcoding a fallback.- filetype: selectedFile.type || "video/mp4", // Use actual file MIME type + filetype: selectedFile.type,
103-128: Require both MIME type and extension to pass validationLine 103 validation still only fails when both checks fail, so a spoofed file with a valid extension but wrong/no MIME type slips through. Reject any file missing a MIME type and require both checks to succeed (log the mismatch before returning an error) so renamed/malicious uploads can’t bypass the client gate.
- const isValidMimeType = SUPPORTED_VIDEO_FORMATS.includes(file.type); - - if (!isValidExtension && !isValidMimeType) { + if (!file.type) { + return { + valid: false, + error: + "Unable to determine file type. Please ensure your file is a valid video format.", + }; + } + + const isValidMimeType = SUPPORTED_VIDEO_FORMATS.includes(file.type); + + if (!isValidExtension || !isValidMimeType) { + console.warn( + "Extension/MIME mismatch", + fileExtension, + file.type + ); return { valid: false, error: `Unsupported video format: ${fileExtension}. Please use MP4, MOV, MKV, WebM, FLV, or TS format with H.264/H.265 codec.`, }; }components/account-dropdown/AccountDropdown.tsx (2)
172-191: Fix ERC-20 addresses to respect the active chain (repeat of prior blocker).Using Base addresses in TOKEN_INFO breaks balances/transfers on other networks. Store per-chain addresses and resolve by current chain before any readContract/sendUserOperation. Also avoid duplicating TokenSymbol; prefer importing from a single source for consistency.
Apply:
-type TokenSymbol = 'ETH' | 'USDC' | 'DAI'; - -const TOKEN_INFO = { - ETH: { - decimals: 18, - symbol: "ETH", - address: null, // Native token - }, - USDC: { - decimals: USDC_TOKEN_DECIMALS, - symbol: "USDC", - address: USDC_TOKEN_ADDRESSES.base, - }, - DAI: { - decimals: DAI_TOKEN_DECIMALS, - symbol: "DAI", - address: DAI_TOKEN_ADDRESSES.base, - }, -} as const; +type TokenSymbol = 'ETH' | 'USDC' | 'DAI'; // consider importing this from lib/sdk/alchemy/swap-service + +const TOKEN_INFO = { + ETH: { + decimals: 18, + symbol: "ETH", + }, + USDC: { + decimals: USDC_TOKEN_DECIMALS, + symbol: "USDC", + addresses: USDC_TOKEN_ADDRESSES, + }, + DAI: { + decimals: DAI_TOKEN_DECIMALS, + symbol: "DAI", + addresses: DAI_TOKEN_ADDRESSES, + }, +} as const;To find other hardcoded usages across the repo:
#!/bin/bash # Scan for hardcoded Base addresses to fix similar issues. rg -n -C2 -S "USDC_TOKEN_ADDRESSES\.base|DAI_TOKEN_ADDRESSES\.base|address:\s*USDC_TOKEN_ADDRESSES\.base|address:\s*DAI_TOKEN_ADDRESSES\.base|TOKEN_INFO.*address:"
529-553: Resolve ERC-20 target address by chain before sending.Sending to Base addresses on other networks will revert. Gate unsupported networks.
- } else { - // Send ERC-20 token (USDC or DAI) - const tokenAmount = parseUnits(sendAmount, tokenInfo.decimals); - - // Encode the transfer calldata - const transferCalldata = encodeFunctionData({ - abi: parseAbi(["function transfer(address,uint256) returns (bool)"]), - functionName: "transfer", - args: [recipientAddress as Address, tokenAmount], - }); - - console.log('Sending ERC-20 transfer:', { - token: selectedToken, - tokenAddress: tokenInfo.address, - recipient: recipientAddress, - amount: tokenAmount.toString(), - }); - - operation = await client!.sendUserOperation({ - uo: { - target: tokenInfo.address as Address, - data: transferCalldata as Hex, - value: BigInt(0), // No native value for ERC-20 transfers - }, - }); - } + } else { + // Send ERC-20 token (USDC or DAI) + const tokenAmount = parseUnits(sendAmount, tokenInfo.decimals); + const chainKey = chain?.id === base.id ? "base" : undefined; + const tokenAddress = chainKey + ? (TOKEN_INFO[selectedToken] as any).addresses?.[chainKey] as Address | undefined + : undefined; + if (!tokenAddress) { + toast({ + variant: "destructive", + title: "Unsupported network", + description: `${selectedToken} is not configured for ${chain?.name}.`, + }); + return; + } + const transferCalldata = encodeFunctionData({ + abi: parseAbi(["function transfer(address,uint256) returns (bool)"]), + functionName: "transfer", + args: [recipientAddress as Address, tokenAmount], + }); + operation = await client!.sendUserOperation({ + uo: { + target: tokenAddress, + data: transferCalldata as Hex, + value: 0n, + }, + }); + }
🧹 Nitpick comments (10)
components/auth/LoginButton.tsx (1)
53-61: Remove debug logging before production.The simplified synchronous approach with error handling via toast is appropriate. However, the
console.logon line 54 should be removed or wrapped in a development-only conditional check to avoid cluttering production logs.Apply this diff to remove the debug statement:
const handleLogin = () => { - console.log('Opening auth modal...', { hasUser: !!user }); try { openAuthModal(); } catch (error) {Alternatively, if you want to keep it for debugging, wrap it in a development check:
const handleLogin = () => { - console.log('Opening auth modal...', { hasUser: !!user }); + if (process.env.NODE_ENV === 'development') { + console.log('Opening auth modal...', { hasUser: !!user }); + } try { openAuthModal();components/Player/Subtitles.tsx (1)
95-96: Remove unused type definition.The
LanguageSelectPropstype is declared but never used in this file or exported for external use.Apply this diff to remove the unused type:
-export type LanguageSelectProps = { - subtitles: Record<string, Chunk>; -}; - export function SubtitlesLanguageSelect() {app/discover/page.tsx (2)
82-88: Move hardcoded membership URL to a constant.The membership URL
"https://join.creativeplatform.xyz"is hardcoded. For maintainability and consistency across the codebase, this should be defined in a constants file.Create a constants file (e.g.,
lib/constants/urls.ts):export const MEMBERSHIP_URL = "https://join.creativeplatform.xyz";Then apply this diff:
+import { MEMBERSHIP_URL } from "@/lib/constants/urls"; + // ... <Link - href="https://join.creativeplatform.xyz" + href={MEMBERSHIP_URL} target="_blank"This same constant can be reused in other components like
MembershipSection.tsx.
114-119: Remove commented-out code.The commented-out livestream section is no longer needed since the functionality has been integrated into the membership-gated "Live Now" section above. Keeping dead code in comments clutters the codebase and can cause confusion.
Apply this diff to remove the commented code:
- {/* Livestream Section - Commented out */} - {/* <div> - <h2 className="my-4 text-2xl font-bold">All Videos</h2> - <LivestreamGrid /> - </div> */} </div>components/Player/TrendingPlayer.tsx (2)
24-37: Centralize AssetMetadata and wire subtitles into providerDefining AssetMetadata inside a component risks duplication/divergence. Move it next to Subtitles (e.g., lib/types/video-asset) and import it here. Also, since you already wrap in SubtitlesProvider, set subtitles from assetMetadata to keep behavior consistent with VideoDetails.
Apply locally in this file:
-import { Subtitles } from "@/lib/types/video-asset"; +import { Subtitles, AssetMetadata } from "@/lib/types/video-asset"; @@ -export interface AssetMetadata { - assetId: string; - playbackId: string; - title: string; - description?: string; - location?: string; - category?: string; - thumbnailUri?: string; - subtitlesUri?: string; - subtitles?: Subtitles; -}Add this effect to initialize subtitles (outside the selected range):
// inside TrendingPlayer component body const { setSubtitles } = useSubtitles(); useEffect(() => { if (assetMetadata?.subtitles) setSubtitles(assetMetadata.subtitles); }, [assetMetadata?.subtitles, setSubtitles]);And define/export AssetMetadata alongside Subtitles in lib/types/video-asset:
export interface AssetMetadata { assetId: string; playbackId: string; title: string; description?: string; location?: string; category?: string; thumbnailUri?: string; subtitlesUri?: string; subtitles?: Subtitles; }Based on relevant code snippets.
21-21: Nit: fix timer ref type for browserUsing NodeJS.Timeout in a client component can mis-type setTimeout. Prefer ReturnType.
Example (outside this range):
const fadeTimeoutRef = useRef<ReturnType<typeof setTimeout>>();components/account-dropdown/AccountDropdown.tsx (4)
99-99: Import viem’s isAddress for safe recipient validation.Needed for the validation below.
-import { parseEther, type Address, type Hex, encodeFunctionData, parseAbi, parseUnits, formatUnits, erc20Abi } from "viem"; +import { parseEther, type Address, type Hex, encodeFunctionData, parseAbi, parseUnits, formatUnits, erc20Abi, isAddress } from "viem";
493-505: Use bigint-safe amount checks and validate inputs.Float comparisons can misfire on large/precise values; also validate address and amount.
- // Check balance - const availableBalance = parseFloat(tokenBalances[selectedToken]); - const requestedAmount = parseFloat(sendAmount); - - if (requestedAmount > availableBalance) { - toast({ - variant: "destructive", - title: "Insufficient Balance", - description: `You have ${availableBalance} ${selectedToken}, but trying to send ${requestedAmount} ${selectedToken}`, - }); - return; - } + // Validate recipient and amount; bigint‑safe balance check + if (!isAddress(recipientAddress)) { + toast({ variant: "destructive", title: "Invalid address", description: "Enter a valid EVM address." }); + return; + } + try { + const decimals = TOKEN_INFO[selectedToken].decimals; + const requested = parseUnits(sendAmount, decimals); + const available = parseUnits(tokenBalances[selectedToken] || "0", decimals); + if (requested === 0n) { + toast({ variant: "destructive", title: "Invalid amount", description: "Amount must be greater than 0." }); + return; + } + if (requested > available) { + toast({ + variant: "destructive", + title: "Insufficient Balance", + description: `You have ${tokenBalances[selectedToken]} ${selectedToken}, but tried to send ${sendAmount} ${selectedToken}`, + }); + return; + } + } catch { + toast({ variant: "destructive", title: "Invalid amount", description: "Enter a valid numeric amount." }); + return; + }
556-574: Guard explorer link for chains without block explorer config.Avoid undefined URL; render action only if available.
- const explorerUrl = `${chain.blockExplorers?.default.url}/tx/${txHash}`; + const explorerBase = chain?.blockExplorers?.default?.url; + const explorerUrl = explorerBase ? `${explorerBase}/tx/${txHash}` : undefined; console.log("Transaction hash:", txHash); toast({ title: "Transaction Successful!", description: "Your transaction has been confirmed.", - action: ( - <ToastAction - altText="View on Explorer" - onClick={() => window.open(explorerUrl, "_blank")} - > - View on Explorer - </ToastAction> - ), + action: explorerUrl ? ( + <ToastAction altText="View on Explorer" onClick={() => window.open(explorerUrl!, "_blank")}> + View on Explorer + </ToastAction> + ) : undefined, });
710-719: Optional: ETH “MAX” should reserve gas if no paymaster.If gas isn’t sponsored, sending full ETH may fail due to fees. Consider subtracting a small buffer (e.g., 0.0002 ETH) from MAX when selectedToken === 'ETH'.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (10)
app/discover/page.tsx(3 hunks)app/page.tsx(1 hunks)components/Player/Subtitles.tsx(1 hunks)components/Player/TrendingPlayer.tsx(1 hunks)components/Videos/Upload/FileUpload.tsx(11 hunks)components/Videos/Upload/index.tsx(5 hunks)components/account-dropdown/AccountDropdown.tsx(14 hunks)components/auth/LoginButton.tsx(1 hunks)components/home-page/NonLoggedInView.tsx(1 hunks)components/home-page/TopVideos.tsx(6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/blockchain-infra.mdc)
**/*.{ts,tsx,js,jsx}: Use only Viem for blockchain interactions (avoid alternative libraries like ethers.js or web3.js)
Use only Account Kit for account-abstraction-related functionality
Leverage Account Abstraction SDKs: @aa-sdk/core, @account-kit/infra, and @account-kit/react for smart account integration, deployment, and usage
Choose the appropriate smart account type; prefer ModularAccountV2 unless project needs dictate LightAccount, MultiOwnerLightAccount, or MultiOwnerModularAccount; note switching types deploys a different account and may require upgrades
Handle bundler and RPC traffic separately by using split transport when calling createSmartAccountClient
Configure gas and fee estimation when creating SmartAccountClient; provide custom gasEstimator and feeEstimator as needed or use alchemyFeeEstimator where appropriate
Manage paymasters for gas sponsorship via paymasterAndData in SmartAccountClient configuration
Implement gas abstraction via Gas Manager API by creating a gas policy and linking its policyId in client configuration
Integrate third-party paymasters correctly; provide custom gasEstimator and paymasterAndData as required; use erc7677Middleware for ERC-7677 compliant paymasters
Utilize viem primitives: createClient for building clients, getContract for contract interactions, and actions for blockchain operations
Understand and correctly handle UserOperationRequest: send, wait for mining, handle failures, and resubmit using drop-and-replace when necessary
Files:
app/page.tsxcomponents/Player/TrendingPlayer.tsxcomponents/account-dropdown/AccountDropdown.tsxcomponents/auth/LoginButton.tsxapp/discover/page.tsxcomponents/home-page/TopVideos.tsxcomponents/Player/Subtitles.tsxcomponents/home-page/NonLoggedInView.tsxcomponents/Videos/Upload/index.tsxcomponents/Videos/Upload/FileUpload.tsx
🧠 Learnings (2)
📚 Learning: 2024-12-04T04:27:45.795Z
Learnt from: liberalterian
PR: creativeplatform/crtv3#80
File: app/components/Videos/Upload/index.tsx:34-36
Timestamp: 2024-12-04T04:27:45.795Z
Learning: When working with Livepeer assets, use the `Asset` type from `'livepeer/models/components'` instead of defining a custom type.
Applied to files:
components/Videos/Upload/index.tsx
📚 Learning: 2024-12-04T05:02:55.124Z
Learnt from: liberalterian
PR: creativeplatform/crtv3#80
File: package.json:19-20
Timestamp: 2024-12-04T05:02:55.124Z
Learning: The use of `kreisler/js-google-translate-free` in `FileUpload.tsx` is a temporary measure and will be replaced with the new `/api/livepeer/translation/route.ts` endpoint.
Applied to files:
components/Videos/Upload/FileUpload.tsx
🧬 Code graph analysis (10)
app/page.tsx (2)
lib/hooks/accountkit/useAuthStateMonitor.ts (1)
useAuthStateMonitor(11-54)components/ui/use-toast.ts (1)
useToast(171-189)
components/Player/TrendingPlayer.tsx (2)
lib/types/video-asset.ts (1)
Subtitles(13-13)components/Videos/VideoDetails.tsx (3)
assetMetadata(547-551)SubtitlesInitializer(540-554)asset(60-66)
components/account-dropdown/AccountDropdown.tsx (6)
lib/sdk/alchemy/swap-service.ts (2)
TokenSymbol(10-10)TOKEN_INFO(13-17)lib/contracts/USDCToken.ts (2)
USDC_TOKEN_DECIMALS(9-9)USDC_TOKEN_ADDRESSES(3-6)lib/contracts/DAIToken.ts (2)
DAI_TOKEN_DECIMALS(9-9)DAI_TOKEN_ADDRESSES(3-6)components/wallet/swap/AlchemySwapWidget.tsx (1)
AlchemySwapWidget(34-1007)components/wallet/balance/MeTokenBalances.tsx (1)
MeTokenBalances(60-258)lib/hooks/metokens/useMeTokensSupabase.ts (1)
useMeTokensSupabase(342-791)
components/auth/LoginButton.tsx (2)
components/ui/use-toast.ts (1)
toast(191-191)components/ui/button.tsx (1)
Button(60-60)
app/discover/page.tsx (2)
components/Videos/VideoSearch.tsx (1)
VideoSearch(50-161)components/account-dropdown/MembershipSection.tsx (2)
MembershipSection(38-141)MembershipDetails(111-111)
components/home-page/TopVideos.tsx (2)
lib/utils/abortUtils.ts (1)
signal(13-15)components/Videos/VideoDetails.tsx (3)
fetchPlaybackSources(55-85)sources(56-59)asset(60-66)
components/Player/Subtitles.tsx (2)
components/Videos/VideoDetails.tsx (3)
SubtitlesInitializer(540-554)assetMetadata(547-551)asset(60-66)app/api/livepeer/utils.ts (1)
SubtitleEntry(1-6)
components/home-page/NonLoggedInView.tsx (3)
lib/hooks/accountkit/useAuthStateMonitor.ts (1)
useAuthStateMonitor(11-54)components/ui/use-toast.ts (1)
useToast(171-189)components/ui/toaster.tsx (1)
Toaster(20-48)
components/Videos/Upload/index.tsx (2)
lib/types/video-asset.ts (1)
VideoAsset(15-47)services/video-assets.ts (2)
updateVideoAsset(143-197)updateVideoAsset(137-170)
components/Videos/Upload/FileUpload.tsx (1)
app/api/livepeer/assetUploadActions.ts (1)
getLivepeerUploadUrl(7-65)
🔇 Additional comments (5)
components/auth/LoginButton.tsx (2)
46-51: LGTM!Proper cleanup logic that prevents stale deployment state after logout. The effect correctly resets
isDeployingwhen the user is no longer authenticated.
63-70: LGTM!The fixed loading label "Deploying Account..." is more accurate and consistent than a variable label. The updated loading condition correctly reflects the removal of the
isLoadingstate.app/discover/page.tsx (2)
1-17: LGTM! Imports are correctly structured.All necessary dependencies for the membership gating and search/filter features are properly imported. The "use client" directive is correctly placed for a component that uses hooks and state.
93-113: Approve VideoCardGrid pagination and search integration
Pagination is implemented internally using ITEMS_PER_PAGE, offset calculation, hasNextPage state, and the Pagination component; VideoSearch props are correctly wired.components/home-page/TopVideos.tsx (1)
264-268: Passing assetId through assetMetadata: LGTMAligns with the new AssetMetadata shape and enables consumers (e.g., views, detail routes).
memebership
remove gas sponsorship
video attestations
verifiable
Implement immediate redirect to Discover page after video upload success.
upload then discover page
Summary by CodeRabbit
New Features
Bug Fixes
Performance
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.